Приложение командной строки JAVA

107
30 июля 2019, 07:30

Есть задача по созданию приложения, обрабатывающего аргументы командной сроки. Строка следующего вида:--file=example.txt add --name==John --surname=Doe --age=33 --email=john@gmail.com, где предлагается рассматривать файл и его имя, как Глобальные опции, далее команда (add, find, clear, delete и т.д.) и, все аргументы, следующие за командой - Локальные опции (обрабатывающие поля конкретной Модели). Приложение должно парсить командную строку и выполнять какую-либо команду. Задачу предлагается решить "чистой" Java, без использования любых библиотек типа commons-cli, picocli и пр. Парсинг строки должен возвращать Map<String, String> globalOptions, Map<String, String> localOptions, String commandName. Но, кроме того, парсинг должен учитывать все имена описания команд и опций, а также "обязательность" использования той или иной опции с каждой конкретной командой. Описания и названия команд и опций должны храниться в классах CommandDescription и OptionDescription соответственно и, затем передаваться в качестве аргументов в метод, осуществляющий парсинг, т.е., например private ParsingResult cmdParser (String[] args, List<OptionDescription> options,List<CommandDescription> commands). Я програмирую совсем недавно и "застрял" на том, как учесть и использовать в парсинге эти List<OptionDescription> options,List<CommandDescription> commands и использовать их далее для выполнения команд? Ниже привожу код, который есть на сегодняшний день: Класс Модели:

public class Person implements Serializable {
private static final long serialVersionUID = -8319950653022309296L;
private static AtomicInteger nextID = new AtomicInteger(0);
private int id;
private String name;
private String surname;
private int age;
private String email;
private Person(int id, String name, String surname, int age, String email) {
    this.id = id;
    this.name = name;
    this.surname = surname;
    this.age = age;
    this.email = email;
}
public Person(String name, String surname, int age, String address) {
    this(nextID.getAndIncrement(), name, surname, age, address);
}
public int getId() {
    return id;
}
public String getName() {
    return name;
}
public String getSurname() {
    return surname;
}
public int getAge() {
    return age;
}
public String getEmail() {
    return email;
}
@Override
public String toString() {
    return "\n\nID: " + getId() + "\nName: " + getName() + "\nSurname: " + getSurname() + "\nAge: " + getAge() + "\nEmail: " + getEmail();
}
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person person = (Person) o;
    return Objects.equals(name, person.name) &&
            Objects.equals(surname, person.surname) &&
            Objects.equals(age, person.age) &&
            Objects.equals(email, person.email);
}
@Override
public int hashCode() {
    return Objects.hash(name, surname, age, email);
}}

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

public class ParsingResult {
Map<String, String> global;
Map<String, String> local;
String commandName;
ParsingResult(Map<String, String> global, Map<String, String> local, String commandName) {
    this.global = global;
    this.local = local;
    this.commandName = commandName;
}
public ParsingResult() {
}
public String getCommandName() {
    return commandName;
}
public void setCommandName(String commandName) {
    this.commandName = commandName;
}}

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

public interface Command {
void execute(Map<String, String> globalOptions, Map<String, String> localOptions);}

Классы описания опций и команд:

public class CommandDescription {
public String nameOfCommand;
private String commandDescription;
private Command command;
private List<OptionDescription> options;
public CommandDescription(String nameOfCommand, String commandDescription, Command command, List<OptionDescription> options) {
    this.nameOfCommand = nameOfCommand;
    this.commandDescription = commandDescription;
    this.command = command;
    this.options = options;
}
public CommandDescription(String nameOfCommand, String commandDescription, Command command) {
    this.nameOfCommand = nameOfCommand;
    this.commandDescription = commandDescription;
    this.command = command;
}
public CommandDescription(String commandName) {
    this.nameOfCommand = commandName;
}
public CommandDescription(String nameOfCommand, String commandDescription) {
    this.nameOfCommand = nameOfCommand;
    this.commandDescription = commandDescription;
}
public CommandDescription() {
}
public String getNameOfCommand() {
    return nameOfCommand;
}
public String getCommandDescription() {
    return commandDescription;
}
public void setNameOfCommand(String nameOfCommand) {
    this.nameOfCommand = nameOfCommand;
}
public void setCommandDescription(String commandDescription) {
    this.commandDescription = commandDescription;
}
@Override
public String toString() {
    return "\nCommand: " + getNameOfCommand();
}}
public class OptionDescription {
private String optionName;
private String optionDescription;
private boolean mandatory;
public OptionDescription(String optionName, String optionDescription, boolean mandatory) {
    this.optionName = optionName;
    this.optionDescription = optionDescription;
    this.mandatory = mandatory;
}
public OptionDescription(String optionName, boolean mandatory) {
    this.optionName = optionName;
    this.mandatory = mandatory;
}
public OptionDescription(String optionName, String optionDescription) {
    this.optionName = optionName;
    this.optionDescription = optionDescription;
}
public OptionDescription(String optionName) {
    this.optionName = optionName;
}
public OptionDescription() {
}
public String getOptionName() {
    return optionName;
}
public String setOptionName(String optionName, boolean mandatory) {
    this.optionName = optionName;
    this.mandatory = mandatory;
    return optionName;
}
public String getOptionDescription() {
    return optionDescription;
}
public void setOptionDescription(String optionDescription) {
    this.optionDescription = optionDescription;
}
@Override
public String toString() {
    return "\nOption: " + getOptionName() + "\nDescription: " + getOptionDescription();
}}

Класс парсинга командной строки:

public class Parser {
private ParsingResult result = new ParsingResult();
private CommandDescription commandDescription = new CommandDescription();
private List<CommandDescription> commands = new ArrayList<>();
private List<OptionDescription> options = new ArrayList<>();
public void parseArguments(String[] args) throws RuntimeException {
    result = cmdParser(args, commands, options);
    Map<String, Class> cmd = new HashMap<>();
    cmd.put("add", AddCommand.class);
    cmd.put("a", AddCommand.class);
    cmd.put("clear", ClearCommand.class);
    cmd.put("c", ClearCommand.class);
    cmd.put("find", FindCommand.class);
    cmd.put("f", FindCommand.class);
    cmd.put("replace", ReplaceCommand.class);
    cmd.put("r", ReplaceCommand.class);
    cmd.put("view", ViewAllCommand.class);
    cmd.put("v", ViewAllCommand.class);
    cmd.put("help", HelpCommand.class);
    cmd.put("h", HelpCommand.class);
    Command command;
    try {
        command = (Command) cmd.get(commandDescription.getNameOfCommand()).getConstructor().newInstance();
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
    System.out.println();
    command.execute(result.global, result.local);
    System.out.println(options.toString());
}
private ParsingResult cmdParser(String[] args, List<CommandDescription> commands, List<OptionDescription> options) {
    OptionDescription op1 = new OptionDescription("file", "file name", true);
    OptionDescription op2 = new OptionDescription("name", "name of person", true);
    OptionDescription op3 = new OptionDescription("surname", "surname of person", true);
    OptionDescription op4 = new OptionDescription("age", "age of person", true);
    OptionDescription op5 = new OptionDescription("email", "person email address", true);
    OptionDescription op6 = new OptionDescription("from", "what needs to be replaced", false);
    OptionDescription op7 = new OptionDescription("to", "what will be replaced", false);
    options.add(op1);
    options.add(op2);
    options.add(op3);
    options.add(op4);
    options.add(op5);
    options.add(op6);
    options.add(op7);
    CommandDescription cd1 = new CommandDescription("add", "added to file", new AddCommand(), options);
    CommandDescription cd2 = new CommandDescription("view", "view all file content", new ViewAllCommand(), options);
    CommandDescription cd3 = new CommandDescription("find", "record search", new FindCommand(), options);
    CommandDescription cd4 = new CommandDescription("replace", "replace content", new ReplaceCommand(), options);
    commands.add(cd1);
    commands.add(cd2);
    commands.add(cd3);
    commands.add(cd4);
    result.global = new LinkedHashMap<>();
    result.local = new LinkedHashMap<>();
    String key;
    String value = null;
    int index;
    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 {
            commandDescription.nameOfCommand = args[i];
            continue;
        }
        index = key.indexOf('=');
        if(index == -1) {
            if((i + 1) < args.length) {
                if(args[i + 1].charAt(0) != '-') {
                    result.local.put(key, args[i + 1]);
                    i++;
                } else {
                    result.local.put(args[i], null);
                }
            } else {
                result.local.put(args[i], null);
            }
        } else {
            value = key.substring(index + 1);
            assertOK(value.length() > 0, "The command has no parameter !!!" + " " + key);
            key = key.substring(0, index);
        }
        if (i == 0){
            result.global.put(key, value);
        }
        if (i > 0){
            result.local.put(key, value);
        }
    }
    return new ParsingResult(result.global, result.local,  result.commandName);
}
private void assertOK(boolean ok, String reason) {
    if (!ok) {
        System.out.println(reason);
        System.exit(-1);
    }
}}

Ну и, класс Main:

public class Main {
public static void main(String[] args) {
    Parser parser = new Parser();
    parser.parseArguments(args);
}}

Классы с логикой выполнения команд для краткости приводить не буду.

Answer 1

Почему-то хочется поменять CommandDescription на enum и наверное можно использовать для валидации команд и поиске. Так же я думаю можно сделать и для OptionDescription, и для каждой CommandDescription по идеи можно выбрать свой набор OptionDescription и при парсинге тоже валидировать

import java.util.Arrays;
import java.util.List;
public enum CommandDescription {
    ADD("add", "a", "added to file", new AddCommand(),
        OptionDescription.FILE,
        OptionDescription.NAME,
        OptionDescription.SURNAME,
        OptionDescription.AGE,
        OptionDescription.EMAIL
        ),
    VIEW("view", "v", "view all file content", new ViewAllCommand()),
    FIND("find", "f", "record search", new FindCommand()),
    REPLACE("replace", "r", "replace content", new ReplaceCommand()),
    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... optionDescription) {
        this.name = name;
        this.alias = alias;
        this.description = description;
        this.command = command;
        this.options = Arrays.asList(optionDescription);
    }
    public static CommandDescription find(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));
    }
    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 "CommandDescription{" +
               "name='" + name + '\'' +
               ", alias='" + alias + '\'' +
               ", description='" + description + '\'' +
               ", command=" + command +
               ", options=" + options +
               '}';
    }
}

OptionDescription

import java.util.Arrays;
public enum OptionDescription {
    FILE("file", "file name", true),
    NAME("name", "name of person", true),
    SURNAME("surname", "surname of person", true),
    AGE("age", "age of person", true),
    EMAIL("email", "person email address", true),
    FROM("from", "what needs to be replaced", false),
    TO("to", "what will be replaced", false);
    private final String option;
    private final String description;
    private final boolean mandatory;
    OptionDescription(String option, String description, boolean mandatory) {
        this.option = option;
        this.description = description;
        this.mandatory = mandatory;
    }
    public static OptionDescription find(String option) {
        return Arrays.stream(values())
                     .filter(optionDescription -> optionDescription.getOption().equals(option))
                     .findFirst()
                     .orElseThrow(() -> new RuntimeException("Option not found for:" + option));
    }
    public String getOption() {
        return option;
    }
    public String getDescription() {
        return description;
    }
    public boolean isMandatory() {
        return mandatory;
    }
    @Override
    public String toString() {
        return "OptionDescription{" +
               "option='" + option + '\'' +
               ", description='" + description + '\'' +
               ", mandatory=" + mandatory +
               '}';
    }
}

Парсер особо не изменял

import java.util.LinkedHashMap;
import java.util.Map;
public class Parser {
    public void parseArguments(String[] args) {
        ParsingResult result = cmdParser(args);
        ValidatorParsingResult validator = new ValidatorParsingResult(result);
        if (validator.validate()) {
            Command command = result.getCommandDescription().getCommand();
            command.execute(result.getGlobal(), result.getLocal());
        }
    }
    private ParsingResult cmdParser(String[] args) {

        Map<OptionDescription, String> global = new LinkedHashMap<>();
        Map<OptionDescription, String> local = new LinkedHashMap<>();
        String commandName = null;
        String key;
        String value = null;
        int index;
        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;
            }
            index = key.indexOf('=');
            if (index == -1) {
                if ((i + 1) < args.length) {
                    if (args[i + 1].charAt(0) != '-') {
                        local.put(OptionDescription.find(key), args[i + 1]);
                        i++;
                    } else {
                        local.put(OptionDescription.find(args[i]), null);
                    }
                } else {
                    local.put(OptionDescription.find(args[i]), null);
                }
            } else {
                value = key.substring(index + 1);
                assertOK(value.length() > 0, "The command has no parameter !!!" + " " + key);
                key = key.substring(0, index);
            }
            if (i == 0) {
                global.put(OptionDescription.find(key), value);
            }
            if (i > 0) {
                local.put(OptionDescription.find(key), value);
            }
        }
        return new ParsingResult(global, local, CommandDescription.find(commandName));
    }
    private void assertOK(boolean ok, String reason) {
        if (!ok) {
            System.out.println(reason);
            System.exit(-1);
        }
    }
}

Контенер для результатов парсинга

import java.util.Map;
public class ParsingResult {
    private final Map<OptionDescription, String> global;
    private final Map<OptionDescription, String> local;
    private final CommandDescription commandDescription;
    public ParsingResult(Map<OptionDescription, String> global, Map<OptionDescription, String> local, CommandDescription commandDescription) {
        this.global = global;
        this.local = local;
        this.commandDescription = commandDescription;
    }
    public Map<OptionDescription, String> getGlobal() {
        return global;
    }
    public Map<OptionDescription, String> getLocal() {
        return local;
    }
    public CommandDescription getCommandDescription() {
        return commandDescription;
    }
}

Валидатор

import java.util.List;
import java.util.Map;
public class ValidatorParsingResult {
    private final ParsingResult result;
    public ValidatorParsingResult(ParsingResult result) {
        this.result = result;
    }
    public boolean validate() {
        CommandDescription command = result.getCommandDescription();
        validateInputOptions(command, result.getGlobal());
        validateInputOptions(command, result.getLocal());
        for (OptionDescription option : command.getOptions()) {
            if (option.isMandatory() &&
                !result.getGlobal().containsKey(option) &&
                !result.getLocal().containsKey(option)) {
                throw new IllegalArgumentException(
                        "For command:\n" + command + "should be provided mandatory option:\n" + option);
            }
        }
        return true;
    }
    private void validateInputOptions(CommandDescription command, Map<OptionDescription, String> inputOptions) {
        List<OptionDescription> options = command.getOptions();
        for (OptionDescription option : inputOptions.keySet()) {
            if (!options.contains(option)) {
                throw new IllegalArgumentException(
                        "The option:\n" + option + " not applicable for command:\n" + command);
            }
        }
    }
}
READ ALSO
Разница между sharecompat.intentbuilder vs action_send

Разница между sharecompat.intentbuilder vs action_send

Подскажите пожалуйста в чем разница? я сейчас новичок в андройде и увидел два метода для шейра текста к примеру через:

141
Каким образом я могу проверить таблицу SQLite на существование нужной мне записи?

Каким образом я могу проверить таблицу SQLite на существование нужной мне записи?

У меня есть форма регистрацииНужно, чтобы если пользователь вводил имя, существующее в базе данных, ему выдавалась ошибка

123
The driver has not received any packets from the server

The driver has not received any packets from the server

У меня есть приложение, которое я подготовил для запуска в dokcer:

110