/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.management.internal.cli.parser.jopt;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import joptsimple.ArgumentAcceptingOptionSpec;
import joptsimple.OptionException;
import joptsimple.OptionParser;
import joptsimple.OptionSpecBuilder;
import org.apache.commons.lang.StringUtils;
import org.apache.geode.management.internal.cli.MultipleValueConverter;
import org.apache.geode.management.internal.cli.exceptions.CliCommandOptionException;
import org.apache.geode.management.internal.cli.exceptions.ExceptionGenerator;
import org.apache.geode.management.internal.cli.parser.Argument;
import org.apache.geode.management.internal.cli.parser.GfshOptionParser;
import org.apache.geode.management.internal.cli.parser.Option;
import org.apache.geode.management.internal.cli.parser.OptionSet;
import org.apache.geode.management.internal.cli.parser.SyntaxConstants;
import org.apache.geode.management.internal.cli.parser.preprocessor.Preprocessor;
import org.apache.geode.management.internal.cli.parser.preprocessor.PreprocessorUtils;
import org.apache.geode.management.internal.cli.parser.preprocessor.TrimmedInput;
import org.apache.geode.management.internal.cli.util.HyphenFormatter;
/**
* Implementation of {@link GfshOptionParser} which internally makes use of
* {@link joptsimple.OptionParser}
*
* Newly constructed JoptOptionParser must be loaded with arguments and options before parsing
* command strings.
*
* @since GemFire 7.0
*/
public class JoptOptionParser implements GfshOptionParser {
private OptionParser parser;
private LinkedList<Argument> arguments = new LinkedList<Argument>();
private LinkedList<Option> options;
/**
* Constructor
*/
public JoptOptionParser() {
parser = new OptionParser(true);
parser.allowsUnrecognizedOptions();
}
public void setArguments(LinkedList<Argument> arguments) {
List<Argument> optional = new LinkedList<Argument>();
// Let us arrange arguments as mandatory arguments
// followed by optional arguments
for (Argument argument : arguments) {
if (argument.isRequired()) {
this.arguments.add(argument);
} else {
optional.add(argument);
}
}
for (Argument argument : optional) {
this.arguments.add(argument);
}
}
public void setOptions(LinkedList<Option> options) {
this.options = options;
for (Option option : options) {
addJoptOptionObject(option);
}
}
private void addJoptOptionObject(Option option) {
OptionSpecBuilder optionBuilder = null;
optionBuilder = parser.acceptsAll(option.getAggregate(), option.getHelp());
/* Now set the the attributes related to the option */
ArgumentAcceptingOptionSpec<String> argumentSpecs = null;
if (option.isWithRequiredArgs()) {
argumentSpecs = optionBuilder.withRequiredArg();
} else {
argumentSpecs = optionBuilder.withOptionalArg();
}
// TODO: temporarily commented out as workaround for GEODE-1598
// if (option.isRequired()) {
// argumentSpecs.required();
// }
if (option.getValueSeparator() != null) {
argumentSpecs.withValuesSeparatedBy(option.getValueSeparator());
}
}
public OptionSet parse(String userInput) throws CliCommandOptionException {
OptionSet optionSet = new OptionSet();
optionSet.setUserInput(userInput != null ? userInput.trim() : "");
if (userInput != null) {
TrimmedInput input = PreprocessorUtils.trim(userInput);
String[] preProcessedInput =
preProcess(new HyphenFormatter().formatCommand(input.getString()));
joptsimple.OptionSet joptOptionSet = null;
CliCommandOptionException ce = null;
// int factor = 0;
try {
joptOptionSet = parser.parse(preProcessedInput);
} catch (OptionException e) {
ce = processException(e);
// TODO: joptOptionSet = e.getDetected(); // removed when geode-joptsimple was removed
}
if (joptOptionSet != null) {
// Make sure there are no miscellaneous, unknown strings that cannot be identified as
// either options or arguments.
if (joptOptionSet.nonOptionArguments().size() > arguments.size()) {
String unknownString = (String) joptOptionSet.nonOptionArguments().get(arguments.size()); // added
// cast
// when
// geode-joptsimple
// was
// removed
// If the first option is un-parseable then it will be returned as "<option>=<value>"
// since it's
// been interpreted as an argument. However, all subsequent options will be returned as
// "<option>".
// This hack splits off the string before the "=" sign if it's the first case.
if (unknownString.matches("^-*\\w+=.*$")) {
unknownString = unknownString.substring(0, unknownString.indexOf('='));
}
// TODO: ce =
// processException(OptionException.createUnrecognizedOptionException(unknownString,
// joptOptionSet)); // removed when geode-joptsimple was removed
}
// First process the arguments
StringBuffer argument = new StringBuffer();
int j = 0;
for (int i = 0; i < joptOptionSet.nonOptionArguments().size()
&& j < arguments.size(); i++) {
argument = argument.append(joptOptionSet.nonOptionArguments().get(i));
// Check for syntax of arguments before adding them to the
// option set as we want to support quoted arguments and those
// in brackets
if (PreprocessorUtils.isSyntaxValid(argument.toString())) {
optionSet.put(arguments.get(j), argument.toString());
j++;
argument.delete(0, argument.length());
}
}
if (argument.length() > 0) {
// Here we do not need to check for the syntax of the argument
// because the argument list is now over and this is the last
// argument which was not added due to improper syntax
optionSet.put(arguments.get(j), argument.toString());
}
// Now process the options
for (Option option : options) {
List<String> synonyms = option.getAggregate();
for (String string : synonyms) {
if (joptOptionSet.has(string)) {
// Check whether the user has actually entered the
// full option or just the start
boolean present = false;
outer: for (String inputSplit : preProcessedInput) {
if (inputSplit.startsWith(SyntaxConstants.LONG_OPTION_SPECIFIER)) {
// Remove option prefix
inputSplit =
StringUtils.removeStart(inputSplit, SyntaxConstants.LONG_OPTION_SPECIFIER);
// Remove value specifier
inputSplit =
StringUtils.removeEnd(inputSplit, SyntaxConstants.OPTION_VALUE_SPECIFIER);
if (!inputSplit.equals("")) {
if (option.getLongOption().equals(inputSplit)) {
present = true;
break outer;
} else {
for (String optionSynonym : option.getSynonyms()) {
if (optionSynonym.equals(inputSplit)) {
present = true;
break outer;
}
}
}
}
}
}
if (present) {
if (joptOptionSet.hasArgument(string)) {
List<?> arguments = joptOptionSet.valuesOf(string);
if (arguments.size() > 1
&& !(option.getConverter() instanceof MultipleValueConverter)
&& option.getValueSeparator() == null) {
List<String> optionList = new ArrayList<String>(1);
optionList.add(string);
// TODO: ce = processException(new
// MultipleArgumentsForOptionException(optionList, joptOptionSet)); // removed
// when geode-joptsimple was removed
} else if ((arguments.size() == 1
&& !(option.getConverter() instanceof MultipleValueConverter))
|| option.getValueSeparator() == null) {
optionSet.put(option, arguments.get(0).toString().trim());
} else {
StringBuffer value = new StringBuffer();
String valueSeparator = option.getValueSeparator();
for (Object object : joptOptionSet.valuesOf(string)) {
if (value.length() == 0) {
value.append((String) object);
} else {
if (valueSeparator != null) {
value.append(valueSeparator + ((String) object).trim());
} else {
value.append(((String) object).trim());
}
}
}
optionSet.put(option, value.toString());
}
} else {
optionSet.put(option, option.getSpecifiedDefaultValue());
}
break;
}
}
}
}
}
// Convert the preProcessedInput into List<String>
List<String> split = new ArrayList<String>();
for (int i = 0; i < preProcessedInput.length; i++) {
split.add(preProcessedInput[i]);
}
optionSet.setNoOfSpacesRemoved(input.getNoOfSpacesRemoved() /* + factor */);
optionSet.setSplit(split);
if (ce != null) {
ce.setOptionSet(optionSet);
throw ce;
}
}
return optionSet;
}
private CliCommandOptionException processException(final OptionException exception) {
return ExceptionGenerator.generate(getOption(exception), exception);
}
private Option getOption(OptionException oe) {
Option exceptionOption = null;
Iterator<String> iterator = oe.options().iterator();
outermost: for (Option option : options) {
/* outer: */for (String string : option.getAggregate()) {
/* inner: */while (iterator.hasNext()) {
String joptOption = iterator.next();
if (string.equals(joptOption)) {
exceptionOption = option;
break outermost;
}
}
}
}
if (exceptionOption == null) {
if (oe.options() != null) {
if (oe.options().size() > 0) {
exceptionOption = new Option(oe.options().iterator().next());
}
}
}
return exceptionOption;
}
private String[] preProcess(String userInput) {
return Preprocessor.split(userInput);
}
public LinkedList<Argument> getArguments() {
return arguments;
}
public LinkedList<Option> getOptions() {
return options;
}
}