Есть некий framework, в котором парсится командная срока. Строка имеет вид: --file=simple.txt add --name=Jane --surname=Doe -age=21 --email=jane@gmail.com
, где add
это исполняющая команда, все, что стоит до команды - глобальные опции, которые нужны каждой(любой) команде, все что после команды - локальные опции, с которыми работает только конкретная команда. Список локальных опций содержится в описании самой команды. Глобальные опции - в отдельном списке. Во время парсинга глобальные и локальные опции сохраняются в двух картах Map<String, String> global, Map<String, String> local
.Сейчас я смог реализовать парсинг так, что только ОДНА опция может быть глобальной (хард кодом написано i == 0
).Вопрос, - как сделать так, чтобы глобальных опций могло быть несколько (в принципе, их может быть сколько угодно).Ниже привожу код.
Класс парсера:
public class Parser {
private static final String LONG_OPTION_PREFIX = "--";
private static final String SHORT_OPTION_PREFIX = "-";
public void parseArguments(String[] args, List<CommandDescription> commands, List<OptionDescription> globalOptions)
throws RuntimeException {
ParsingResult result = cmdParser(args, commands, globalOptions);
ParsingValidator validator = new ParsingValidator();
if (validator.validate(result)) {
Command command = result.getCommandDescription().getCommand();
command.execute(result.getGlobal(), result.getLocal());
}
}
private ParsingResult cmdParser(String[] args, List<CommandDescription> commands, List<OptionDescription> globalOptions) {
ParsingValidator validator = new ParsingValidator();
Map<String, String> global = new LinkedHashMap<>();
Map<String, String> local = new LinkedHashMap<>();
String key;
String value;
int separatorIndex;
String commandName = null;
CommandDescription command = null;
List<OptionDescription> localOptions = null;
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith(SHORT_OPTION_PREFIX)) {
if (args[i].startsWith(LONG_OPTION_PREFIX)) {
assertOK(args[i].length() > 2, "Empty long argument in the string:" + " " +
"'" + args[i] + "'");
key = args[i].substring(2);
} else {
assertOK(args[i].length() > 2, "Empty short argument in the string:" + " " +
"'" + args[i] + "'");
assertOK(args[i].charAt(2) == '=', "Short argument " + args[i] + " - must have one " +
"character, e.g. '-x'");
key = args[i].substring(1);
}
separatorIndex = key.indexOf('=');
value = key.substring(separatorIndex + 1);
assertOK(value.length() > 0, "The option has no parameter !!!" + " " + key);
assertOK(separatorIndex > 0, "The option has no parameter !!!" + " " + key);
key = key.substring(0, separatorIndex);
if (i == 0){
global.put(validator.findOption(globalOptions, key), value);
} else if (command == null) {
throw new IllegalArgumentException("You have to enter command name after global arguments");
} else {
local.put(validator.findOption(localOptions, key), value);
}
} else if (commandName == null) {
commandName = args[i];
command = validator.findCommand(commands, commandName);
localOptions = command.getLocalOptions();
System.out.println(command);
} else {
throw new IllegalArgumentException("Command already exists, you can enter only single command");
}
}
return new ParsingResult(global, local, command, globalOptions);
}
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 CommandDescription commandDescription;
private List<OptionDescription> globalOptions;
ParsingResult(Map<String, String> global, Map<String, String> local, CommandDescription commandDescription,
List<OptionDescription> globalOptions) {
this.global = global;
this.local = local;
this.commandDescription = commandDescription;
this.globalOptions = globalOptions;
}
Map<String, String> getGlobal() {
return global;
}
Map<String, String> getLocal() {
return local;
}
CommandDescription getCommandDescription() {
return commandDescription;
}
public List<OptionDescription> getGlobalOptions() {
return globalOptions;
}}
Поиск команды и опции и их валидация:
class ParsingValidator {
CommandDescription findCommand(List<CommandDescription> commands, String commandName){
return commands.stream().filter(commandDescription ->
commandDescription.getName().equals(commandName)
|| commandDescription.getAlias().equals(commandName))
.findFirst()
.orElseThrow(() -> new RuntimeException("Command not found for: " + commandName));
}
String findOption(List<OptionDescription> options, String option){
return options.stream().filter(optionDescription ->
optionDescription.getLongOptionName().equals(option)
|| optionDescription.getShortOptionName().equals(option))
.findFirst()
.orElseThrow(() -> new RuntimeException("Option not found for: " + option))
.getLongOptionName();
}
boolean validate(ParsingResult result){
CommandDescription commandDescription = result.getCommandDescription();
return validateMandatoryOptions(result.getGlobalOptions(), result.getGlobal(), commandDescription) &&
validateMandatoryOptions(commandDescription.getLocalOptions(), result.getLocal(), commandDescription);
}
private boolean validateMandatoryOptions(List<OptionDescription> commandOptions, Map<String, String> parsedOptions,
CommandDescription commandDescription) {
for (OptionDescription option : commandOptions){
if (!parsedOptions.containsKey(option.getLongOptionName()) && option.isMandatory()) {
throw new IllegalArgumentException
("For command:\n" + commandDescription + "\n\nshould be provided mandatory option:\n" + option);
}
}
return true;
}}
Интерфейс, подписывающий метод, котрорый использует каждая команда для выполнения:
public interface Command {
void execute(Map<String, String> globalOptions, Map<String, String> localOptions);}
Класс описания опций:
public class OptionDescription {
private String longOptionName;
private String shortOptionName;
private String description;
private boolean mandatory;
public OptionDescription(String option, String shortOptionName, String description, boolean mandatory) {
this.longOptionName = option;
this.shortOptionName = shortOptionName;
this.description = description;
this.mandatory = mandatory;
}
public String getLongOptionName() {
return longOptionName;
}
public String getShortOptionName() {
return shortOptionName;
}
private String getDescription() {
return description;
}
public boolean isMandatory() {
return mandatory;
}
@Override
public String toString() {
return "\nOption: " + "'" + getLongOptionName() + "'" + " or " + "'" + getShortOptionName() +
"'" + "\nDescription: " + getDescription();
}}
Класс описания команд:
public class CommandDescription {
private String name;
private String alias;
private String description;
private Command command;
private List<OptionDescription> localOptions;
public CommandDescription(String name, String alias, String description, Command command, OptionDescription... localOptions) {
this.name = name;
this.alias = alias;
this.description = description;
this.command = command;
this.localOptions = Arrays.asList(localOptions);
}
public String getName() {
return name;
}
public String getAlias() {
return alias;
}
private String getDescription() {
return description;
}
public Command getCommand() {
return command;
}
public List<OptionDescription> getLocalOptions() {
return localOptions;
}
@Override
public String toString() {
return "\nCommand: " + "'" + getName() + "'" + " or " + "'" + getAlias() + "'" + " - " + getDescription()
+ "\nmandatory or optional options: " + getLocalOptions();
}}
Вы Apache Commons CLI уже видели?
Виртуальный выделенный сервер (VDS) становится отличным выбором
недавно начал пользоваться JavaConfig в SpringИ сейчас решил попировать провести тесты, без использования xml
Есть задача: Необходимо написать класс который сериализует/десериализует Java BeansВ случае наличия циклических ссылок выкинуть exception
Делаю авторизацию в вк по этому видео