package org.springframework.roo.shell;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import static org.springframework.roo.shell.CliOption.*;
/**
* Utilities for parsing.
*
* @author Ben Alex
* @since 1.0
*/
public class ParserUtils {
private static void store(final Map<String, String> results, final StringBuilder currentOption,
final StringBuilder currentValue) {
if (currentOption.length() > 0) {
// There is an option marker
final String option = currentOption.toString();
Validate.isTrue(!results.containsKey(option), "You cannot specify option '" + option
+ "' more than once in a single command");
results.put(option, currentValue.toString());
} else {
// There was no option marker, so verify this isn't the first
Validate.isTrue(!results.containsKey(""),
"You cannot add more than one default option ('%s') in a single command",
currentValue.toString());
results.put("", currentValue.toString());
}
}
/**
* Converts a particular buffer into a tokenized structure.
* <p>
* Properly treats double quotes (") as option delimiters.
* <p>
* Expects option names to be preceded by a single or double dash. We call
* this an "option marker".
* <p>
* Treats spaces as the default option tokenizer.
* <p>
* Any token without an option marker is considered the default. The default
* is returned in the Map as an element with an empty string key (""). There
* can only be a single default.
*
* @param remainingBuffer to tokenize
* @return a Map where keys are the option names (minus any dashes) and
* values are the option values (any double-quotes are removed)
*/
public static Map<String, String> tokenize(final String remainingBuffer) {
Validate.notNull(remainingBuffer, "Remaining buffer cannot be null, although it can be empty");
final Map<String, String> result = new LinkedHashMap<String, String>();
StringBuilder currentOption = new StringBuilder();
StringBuilder currentValue = new StringBuilder();
boolean inQuotes = false;
// Verify correct number of double quotes are present
int count = 0;
for (final char c : remainingBuffer.toCharArray()) {
if ('"' == c) {
count++;
}
}
Validate.isTrue(count % 2 == 0, "Cannot have an unbalanced number of quotation marks");
if ("".equals(remainingBuffer.trim())) {
// They've not specified anything, so exit now
return result;
}
final String[] split = remainingBuffer.split(" ");
for (int i = 0; i < split.length; i++) {
final String currentToken = split[i];
if (currentToken.startsWith("\"") && currentToken.endsWith("\"") && currentToken.length() > 1) {
final String tokenLessDelimiters = currentToken.substring(1, currentToken.length() - 1);
currentValue.append(tokenLessDelimiters);
// If the current value is an empty string that means the
// user has explicitly set it as such so mark it as empty
// so that it doesn't get replaced by null or a default
// value during parsing.
if ("".equals(currentValue.toString())) {
currentValue.append(EMPTY);
}
// Store this token
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
continue;
}
if (inQuotes) {
// We're only interested in this token series ending
if (currentToken.endsWith("\"")) {
final String tokenLessDelimiters = currentToken.substring(0, currentToken.length() - 1);
currentValue.append(" ").append(tokenLessDelimiters);
inQuotes = false;
// Store this now-ended token series
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
} else {
// The current token series has not ended
currentValue.append(" ").append(currentToken);
}
continue;
}
if (currentToken.startsWith("\"")) {
// We're about to start a new delimited token
final String tokenLessDelimiters = currentToken.substring(1);
currentValue.append(tokenLessDelimiters);
inQuotes = true;
continue;
}
if (currentToken.trim().equals("")) {
// It's simply empty, so ignore it (ROO-23)
continue;
}
if (currentToken.startsWith("--")) {
// We're about to start a new option marker
// First strip all of the - or -- or however many there are
final int lastIndex = currentToken.lastIndexOf("-");
final String tokenLessDelimiters = currentToken.substring(lastIndex + 1);
currentOption.append(tokenLessDelimiters);
// Store this token if it's the last one, or the next token
// starts with a "-"
if (i + 1 == split.length) {
// We're at the end of the tokens, so store this one and
// stop processing
store(result, currentOption, currentValue);
break;
}
if (split[i + 1].startsWith("-")) {
// A new token is being started next iteration, so store
// this one now
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
}
continue;
}
// We must be in a standard token
// If the standard token has no option name, we allow it to contain
// unquoted spaces
if (currentOption.length() == 0) {
if (currentValue.length() > 0) {
// Existing content, so add a space first
currentValue.append(" ");
}
currentValue.append(currentToken);
// Store this token if it's the last one, or the next token
// starts with a "-"
if (i + 1 == split.length) {
// We're at the end of the tokens, so store this one and
// stop processing
store(result, currentOption, currentValue);
break;
}
if (split[i + 1].startsWith("--")) {
// A new token is being started next iteration, so store
// this one now
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
}
continue;
}
// This is an ordinary token, so store it now
currentValue.append(currentToken);
store(result, currentOption, currentValue);
currentOption = new StringBuilder();
currentValue = new StringBuilder();
}
// Strip out an empty default option, if it was returned (ROO-379)
if (result.containsKey("") && result.get("").trim().equals("")) {
result.remove("");
}
return result;
}
private ParserUtils() {}
}