/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.jcwhatever.nucleus.internal.managed.commands;
import com.jcwhatever.nucleus.collections.RetrievableSet;
import com.jcwhatever.nucleus.managed.commands.IRegisteredCommand;
import com.jcwhatever.nucleus.managed.commands.exceptions.CommandException;
import com.jcwhatever.nucleus.managed.commands.parameters.ICommandParameter;
import com.jcwhatever.nucleus.managed.commands.parameters.IFlagParameter;
import com.jcwhatever.nucleus.utils.ArrayUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import java.util.ArrayDeque;
import java.util.Deque;
/**
* Parses command arguments.
*/
class ArgumentParser {
// common floating argument prefix, all floating arguments begin with this
public static final String COMMON_PREFIX = "-";
// floating argument prefix
public static final String FLOATING_PREFIX = COMMON_PREFIX;
// flag prefix
public static final String FLAG_PREFIX = COMMON_PREFIX + '-';
/**
* Parse command arguments
*
* @param command The command the arguments are for.
* @param args The arguments.
*/
public ArgumentParseResults parse(IRegisteredCommand command, String[] args)
throws CommandException {
PreCon.notNull(command);
PreCon.notNull(args);
ArgumentParseResults results = new ArgumentParseResults(command);
Deque<String> arguments = ArrayUtils.toDeque(args);
Deque<ICommandParameter> staticParameters = new ArrayDeque<>(
command.getInfo().getStaticParams());
// parse arguments for static parameters.
parseStaticArgs(command, results, staticParameters, arguments);
// check if there are floating parameters for the command.
if (command.getInfo().getRawFloatingParams().length == 0 &&
command.getInfo().getRawFlagParams().length == 0) {
// if there are no more parameters but there are still
// arguments left, then there are too many arguments.
if (!arguments.isEmpty())
throw CommandException.tooManyArgs(command);
// nothing left to do
return results;
}
// get ready to parse floating and flag parameters.
RetrievableSet<ICommandParameter> floating = new RetrievableSet<>(command.getInfo().getFloatingParams());
RetrievableSet<IFlagParameter> flags = new RetrievableSet<>(command.getInfo().getFlagParams());
// parse arguments for non static parameters.
parseNonStaticArgs(command, results, floating, flags, arguments);
return results;
}
// parse arguments for static parameters
private void parseStaticArgs(IRegisteredCommand command,
ArgumentParseResults results,
Deque<ICommandParameter> staticParameters,
Deque<String> arguments)
throws CommandException {
while (!staticParameters.isEmpty()) {
ICommandParameter parameter = staticParameters.removeFirst();
String name = parameter.getName();
String value = null;
if (!arguments.isEmpty()) {
String arg = arguments.removeFirst();
// Should not be any floating or flag parameters before required arguments
if (arg.startsWith(COMMON_PREFIX)) {
// the last static parameter might be optional, in which
// case it should have a default value.
// If there are still more static parameters, it means this
// is not the last parameter and the value is incorrect.
if (!staticParameters.isEmpty()) {
throw CommandException.invalidArgument(command, parameter.getName());
}
// No default value defined means a discreet value is expected.
else if (!parameter.hasDefaultValue()) {
throw CommandException.missingRequiredArg(command, parameter);
}
// re-insert floating argument so the other parsers
// will see it. Since this is the last static parameter,
// this is the end of the loop.
else {
arguments.addFirst(arg);
}
}
else {
// get parameter value
value = parseArgValue(arg, arguments);
}
}
// add argument
if (value != null || parameter.hasDefaultValue()) {
Argument argument = new Argument(parameter, value);
if (results.getArgMap().containsKey(name))
throw CommandException.duplicateArg(command, parameter);
results.getStaticArgs().add(argument);
results.getArgMap().put(name, argument);
}
else {
throw CommandException.missingRequiredArg(command, parameter);
}
}
}
// parse arguments for floating parameters and flags
private void parseNonStaticArgs(IRegisteredCommand command,
ArgumentParseResults results,
RetrievableSet<ICommandParameter> parameters,
RetrievableSet<IFlagParameter> flags,
Deque<String> arguments)
throws CommandException {
while (!arguments.isEmpty()) {
String paramName = arguments.removeFirst();
if (!paramName.startsWith(COMMON_PREFIX))
throw CommandException.invalidArgument(command, paramName);
// check for flag
if (paramName.startsWith(FLAG_PREFIX)) {
paramName = paramName.substring(FLAG_PREFIX.length());
IFlagParameter parameter = flags.removeRetrieve(new Flag(paramName, -1));
if (parameter == null)
throw CommandException.invalidFlag(command, paramName);
results.setFlag(paramName, true);
}
// check if floating parameter
else if (paramName.startsWith(FLOATING_PREFIX)) {
paramName = paramName.substring(FLOATING_PREFIX.length());
ICommandParameter parameter = parameters.removeRetrieve(new Parameter(paramName, null));
if (parameter == null)
throw CommandException.invalidParam(command, paramName);
if (arguments.isEmpty())
throw CommandException.missingRequiredArg(command, parameter);
String arg = parseArgValue(arguments.removeFirst(), arguments);
Argument commandArgument = new Argument(parameter, arg);
if (results.getArgMap().containsKey(paramName))
throw CommandException.duplicateArg(command, parameter);
results.getFloatingArgs().add(commandArgument);
results.getArgMap().put(paramName, commandArgument);
}
}
if (!parameters.isEmpty()) {
for (ICommandParameter param : parameters) {
if (param.hasDefaultValue()) {
Argument commandArgument = new Argument(param, null);
results.getFloatingArgs().add(commandArgument);
results.getArgMap().put(param.getName(), commandArgument);
} else {
throw CommandException.missingRequiredArg(command, param);
}
}
}
}
/**
* parses quotes if present or returns current argument.
*
* @param currentArg The current argument
* @param argsQueue The queue of arguments words
*/
private String parseArgValue(String currentArg, Deque<String> argsQueue) {
// check to see if parsing a literal
String quote = null;
// detect double quote
if (currentArg.startsWith("\"")) {
quote = "\"";
}
// detect single quote
else if (currentArg.startsWith("'")) {
quote = "'";
}
// check for quoted literal
if (quote != null) {
String firstWord = currentArg.substring(1); // remove quotation
// make sure the literal isn't closed on the same word
if (firstWord.endsWith(quote)) {
// remove end quote
return firstWord.substring(0, firstWord.length() - 1);
}
// otherwise parse ahead until end of literal
else {
StringBuilder literal = new StringBuilder(currentArg.length() * argsQueue.size());
literal.append(firstWord);
while (!argsQueue.isEmpty()) {
String nextArg = argsQueue.removeFirst();
// check if this is the final word in the literal
if (nextArg.endsWith(quote)) {
//remove end quote
nextArg = nextArg.substring(0, nextArg.length() - 1);
literal.append(' ');
literal.append(nextArg);
break;
}
literal.append(' ');
literal.append(nextArg);
}
return literal.toString();
}
}
// value is unquoted argument
else {
return currentArg;
}
}
}