All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodiac.sdk.cli.Parser Maven / Gradle / Ivy

package org.zodiac.sdk.cli;

import io.vavr.control.Either;
import io.vavr.control.Try;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public final class Parser {
    Class clazz;
    Checker checker;

    public Parser(final Class clazz) {
        this.clazz = clazz;
        checker = new Checker<>(clazz);
    }

    public Try> parse(final String in) {
        return Try.of(() -> tokenize(in));
    }

    private Either tokenize(final String in) throws ParseError {
        try {
            final T instance = make(clazz);

            final Command command;
            SubCommand subCommand = null;
            Argument argument = null;

            List lexemes = splitBySpace(in);

            command = checker.command(lexemes.get(0));
            if (command == null) {
                throw new ParseError(lexemes.get(0) + " is not a valid command!");
            }
            lexemes.remove(0);

            if (lexemes.get(0).equals("help")) {
                if (lexemes.size() > 1 && checker.isSubCommand(lexemes.get(1))) {
                    return Either.left(checker.help(checker.subCommand(lexemes.get(1))));
                }
                return Either.left(checker.help());
            }

            if (!checker.subCommands.isEmpty()) {
                subCommand = checker.subCommand(lexemes.get(0));
                if (subCommand == null) {
                    throw new ParseError(lexemes.get(0) + " is not a valid subCommand!");
                }
                lexemes.remove(0);

                if (lexemes.get(0).equals("help")) {
                    return Either.left(checker.help(subCommand));
                }

                final String lastLexeme = lexemes.get(lexemes.size() - 1);
                if (checker.isArgument(subCommand, lastLexeme)) {
                    argument = checker.argument(subCommand, lastLexeme);
                    if (argument == null) {
                        throw new ParseError(lastLexeme + " is not a valid subCommand argument for + " + subCommand.name() + "!");
                    }

                    setField(instance, clazz, subCommand.name(), lastLexeme);
                }
                lexemes.remove(lexemes.get(lexemes.size() - 1));
            }

            lexemes = splitByFlagOrOption(String.join(" ", lexemes));

            for (int i = 0; i < lexemes.size(); i++) {
                final String lexeme = lexemes.get(i);
                final String nextLexeme = i <= lexemes.size() - 2 ? lexemes.get(i + 1) : null;

                if (checker.isFlag(lexeme)) {
                    setField(instance, clazz, checker.flag(lexeme).name(), true);
                } else if (checker.isOption(lexeme)) {
                    final Option option = checker.option(lexeme);
                    if (nextLexeme == null) {
                        throw new ParseError("No valid argument was found following the " + option.name() + " option!");
                    }

                    final Argument arg = checker.argument(option, nextLexeme);
                    if (arg == null) {
                        throw new ParseError(nextLexeme + " is not a valid argument for option " + option.name() + "!");
                    }

                    if (clazz.getDeclaredField(option.name()).getType() == String.class) {
                        setField(instance, clazz, option.name(), nextLexeme);
                    } else if (clazz.getDeclaredField(option.name()).getType() == List.class) {
                        @SuppressWarnings("unchecked")
                        List value = (List) getField(instance, clazz, option.name());
                        value = value == null ? new ArrayList<>() : value;
                        value.add(nextLexeme);
                        setField(instance, clazz, option.name(), value);
                    } else if (clazz.getDeclaredField(option.name()).getType().toString().equals("int")) {
                        setField(instance, clazz, option.name(), Integer.parseInt(nextLexeme));
                    } else {
                        System.out.println("Warning: " + option.name() + " was neither a String or List, and hence could not be set.");
                    }
                    i++;
                } else if (checker.isArgument(subCommand, lexeme)) {
                    if (argument != null) {
                        throw new ParseError("More than one argument for subCommand " + subCommand.name() + " was found!");
                    }
                    argument = checker.argument(subCommand, lexeme);
                    if (argument == null) {
                        throw new ParseError(lexeme + " is not a valid subCommand argument for + " + subCommand.name() + "!");
                    }

                    setField(instance, clazz, subCommand.name(), lexeme);
                } else if (checker.isSubCommand(lexeme)) {
                    throw new ParseError("Second subCommand specified: " + lexeme + ". SubCommand '" + subCommand.name() + "' was already supplied.");
                } else {
                    throw new ParseError(lexeme + " is not a valid flag or option!");
                }
            }

            return Either.right(instance);
        } catch (final Exception e) {
            throw new ParseError(e.getMessage(), e);
        }
    }

    public String help() {
        return checker.help();
    }

    public String help(final SubCommand subCommand) {
        return checker.help(subCommand);
    }

    private static  T make(final Class clazz) throws ParseError {
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (final InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new ParseError("The class " + clazz.getSimpleName() + " could not be instantiated for some reason. Please make sure that the annotations were used properly.");
        }
    }

    private static  void setField(
        final T instance,
        final Class clazz,
        final String name,
        final Object value) throws NoSuchFieldException, IllegalAccessException {
        final Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        field.set(instance, value);
    }

    private static  Object getField(
        final T instance,
        final Class clazz,
        final String name) throws IllegalAccessException, NoSuchFieldException {
        final Field field = clazz.getDeclaredField(name);
        field.setAccessible(true);
        return field.get(instance);
    }

    private List splitByFlagOrOption(final String in) {
        final String command = checker.command.name();
        final List subCommands = checker.subCommands.stream().map(SubCommand::name).collect(Collectors.toList());
        final List flags = checker.flags.stream().flatMap(e -> Arrays.stream(e.alias())).collect(Collectors.toList());
        final List options = checker.options.stream().flatMap(e -> Arrays.stream(e.alias())).collect(Collectors.toList());
        final List tokens = new ArrayList<>();
        tokens.add(command);
        tokens.addAll(subCommands);
        tokens.addAll(flags);
        tokens.addAll(options);
        final List args = new ArrayList<>();

        boolean lastWasToken = false;
        for (final String arg : splitBySpace(in)) {
            final int idx = args.size() - 1;

            final boolean isToken = tokens.contains(arg);
            if (isToken) {
                args.add(arg);
            } else if (!lastWasToken) {
                if (idx > -1) {
                    args.set(idx, args.get(idx) + " " + arg);
                } else {
                    args.add(arg);
                }
            } else {
                args.add(arg);
            }

            lastWasToken = isToken;
        }
        return args;
    }

    private static List splitBySpace(final String in) {
        return Arrays.stream(in.split(" ")).filter(e -> !e.trim().equals("")).collect(Collectors.toList());
    }

    private static  List annotationsWithType(final Class cls, final Class annotation) {
        return Arrays.stream(cls.getDeclaredFields())
            .flatMap(e -> Arrays.stream(e.getAnnotationsByType(annotation)))
            .collect(Collectors.toList());
    }

    static class Checker {
        Command command;

        List subCommands;

        List flags;

        List




© 2015 - 2024 Weber Informatics LLC | Privacy Policy