Валидация аргументов командной строки JAVA

170
13 августа 2019, 22:10

Разрабатывается приложение командной строки, где строка следующего вида --file=db.txt add --name=John --surname=Doe --age=27 --email=john@gmail.com или в коротком варианте: -file=db.txt a -n=John -s=Doe -a=27 -e=john@gmail.com, парсится с помощью маппинга. В строке имеется команда (например, "add" или "а"), логика всех команд описывается в отдельных объектах, имплементирующих один интерфейс, подписывающий единственный метод: execute(Map<String, String> globalOptions, Map<String, String> localOptions. Описание всех команд и всех ее опций , хранится в Перечислениях - public enum CommandDescription и public enum OptionDescription. Каждая команда имеет свой набор опций, с которыми она работает (например команда add должна использовать --name, --surname, --age, --email, а например, команда replace, только опции --from, --to). Занимаюсь программированием недавно и, никак не могу понять, как реализовать в парсинге проверку на "обязательность" или "необязательность" опций для каждой команды. Ниже привожу код, который есть на сегодняшний день. Парсинг:

public class Parser {
public void parseArguments(String[] args) throws RuntimeException {
    ParsingResult result = cmdParser(args);
    Command command = findCommand(result.getCommandName());
    command.execute(result.getGlobal(), result.getLocal());
}
private ParsingResult cmdParser(String[] args) {
    Map<String, String> global = new LinkedHashMap<>();
    Map<String, String> local = new LinkedHashMap<>();
    String key;
    String value = null;
    int separatorIndex;
    String commandName = null;
    for (int i = 0; i < args.length; i++) {
        if (args[i].startsWith("--")) {
            assertOK(args[i].length() > 2, "Empty long argument in the string:" + " " + "'" + args[i] + "'");
            key = args[i].substring(2);
        } else if(args[i].startsWith("-")) {
            assertOK(args[i].length() > 1, "Empty short argument in the string:" + " " + "'" + args[i] + "'");
            key = args[i].substring(1);
        } else {
            commandName = args[i];
            continue;
        }
        separatorIndex = key.indexOf('=');
        if(separatorIndex == -1) {
            if((i + 1) < args.length) {
                if(args[i + 1].charAt(0) != '-') {
                    local.put(key, args[i + 1]);
                    i++;
                } else {
                    throw new RuntimeException("There is no value for the " + "'" + key + "'" + " option!");
                }
            } else {
                throw new RuntimeException("There is no value for the " + "'" + key + "'" + " option!");
            }
        } else {
            value = key.substring(separatorIndex + 1);
            assertOK(value.length() > 0, "The command has no parameter !!!" + " " + key);
            key = key.substring(0, separatorIndex);
        }
        if (i == 0){
            global.put(key, value);
        }
        if (i > 0){
            local.put(key, value);
        }
    }
    return new ParsingResult(global, local, commandName);
}
private void assertOK(boolean ok, String reason) {
    if (!ok) {
        throw new RuntimeException(reason);
    }
}}

Хранение результата парсинга:

class ParsingResult {
private Map<String, String> global;
private Map<String, String> local;
private String commandName;
ParsingResult(Map<String, String> global, Map<String, String> local, String commandName) {
    this.global = global;
    this.local = local;
    this.commandName = commandName;
}
Map<String, String> getGlobal() {
    return global;
}
Map<String, String> getLocal() {
    return local;
}
String getCommandName() {
    return commandName;
}}

Перечисления, для хранения команд и их опций:

public enum CommandDescription {
ADD("add", "a", "added to file", new AddCommand(), FILE, NAME, SURNAME, AGE, EMAIL),
CLEAR("clear", "c", "full clear of file", new ClearCommand(), FILE),
VIEW("view", "v", "view all file content", new ViewAllCommand(), FILE),
FIND("find", "f", "record search", new FindCommand(), FILE, NAME, SURNAME, AGE, EMAIL, ID),
REPLACE("replace", "r", "replace content", new ReplaceCommand(), FILE, FROM, TO),
HELP("help", "h", "help", new HelpCommand());
private final String name;
private final String alias;
private final String description;
private final Command command;
private final List<OptionDescription> options;
CommandDescription(String name, String alias, String description, Command command, OptionDescription... options) {
    this.name = name;
    this.alias = alias;
    this.description = description;
    this.command = command;
    this.options = Arrays.asList(options);
}
public static Command findCommand(String commandName) {
    return Arrays.stream(values())
            .filter(commandDescription ->
                    commandDescription.name.equals(commandName) ||
                            commandDescription.alias.equals(commandName))
            .findFirst()
            .orElseThrow(() -> new RuntimeException("Command not found for:" + commandName))
            .getCommand();
}
public String getName() {
    return name;
}
public String getAlias() {
    return alias;
}
public String getDescription() {
    return description;
}
public Command getCommand() {
    return command;
}
public List<OptionDescription> getOptions() {
    return options;
}
@Override
public String toString() {
    return "\nCommand: " + getName() + " - " + getDescription() + "\nmandatory or optionals options: " + getOptions();
}}
public enum OptionDescription {
FILE("file", "file", "file name", true),
NAME("name", "n", "name of person", true),
SURNAME("surname", "s", "surname of person", true),
AGE("age", "a", "age of person", true),
EMAIL("email", "e", "person email address", true),
FROM("from", "from", "what needs to be replaced", true),
TO("to", "to", "what will be replaced", true),
ID("id", "id", "ID of person", true);
private final String option;
private final String shortOptionName;
private final String description;
private final boolean mandatory;
OptionDescription(String option, String shortOptionName, String description, boolean mandatory) {
    this.option = option;
    this.shortOptionName = shortOptionName;
    this.description = description;
    this.mandatory = mandatory;
}
public String getOption() {
    return option;
}
public String getShortOptionName() {
    return shortOptionName;
}
public String getDescription() {
    return description;
}
public boolean isMandatory() {
    return mandatory;
}
@Override
public String toString() {
    return "\nOption: " + getOption() + "\nDescription: " + getDescription();
}}

Интерфейс, подписывающий метод выполнения каждой команды:

public interface Command {
void execute(Map<String, String> globalOptions, Map<String, String> localOptions);}
READ ALSO
Jamod, детектировать изменение processImage \ детектировать наличие обмена

Jamod, детектировать изменение processImage \ детектировать наличие обмена

Создан Slave TCP Modbus при помощи библиотеки Jamod (пробовал так же j2mod с тем же результатом) согласно документацииРаботает, обмен идет, но есть пара...

97
@Query. QuerySyntaxException: Invalid path: &#39;b.userIP&#39;

@Query. QuerySyntaxException: Invalid path: 'b.userIP'

У меня есть запрос в SQLЗапрос работает

111
java.lang.StackOverflowError: stack size 8MB

java.lang.StackOverflowError: stack size 8MB

приложение для чтения новостей на котлинеиспользуется newsapi

113
Помогите разобраться с наследованием

Помогите разобраться с наследованием

Не понимаю принцип работы upcast

128