/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. 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 com.cloudera.flume.shell;
import java.util.ArrayList;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.CommonTree;
import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.master.Command;
import com.cloudera.flume.shell.antlr.FlumeShellLexer;
import com.cloudera.flume.shell.antlr.FlumeShellParser;
/**
* Builds a command object from a string. Needed for sane command line parsing.
*
* Unquoted tokens can contain alphanumeric, '.',':','_', or '-'. Tokens
* enclosed in '"' will be java string unescaped. Tokens enclosed in ''' (single
* quotes) are not unescaped at all and concontain any char except for '''.
* Exceptions are thrown if quotes are not properly matched or invalid chars
* present in unquoted tokens. .
*/
public class CommandBuilder {
public static final Logger LOG = LoggerFactory.getLogger(CommandBuilder.class);
enum ASTNODE {
CMD, DQUOTE, SQUOTE, STRING
};
/**
* This hooks a particular string to the lexer. From there it creates a parser
* that can be started from different entities. The lexer and language are
* case sensitive.
*/
static FlumeShellParser getShellCmdParser(String s) {
FlumeShellLexer lexer = new FlumeShellLexer(new ANTLRStringStream(s));
CommonTokenStream tokens = new CommonTokenStream(lexer);
return new FlumeShellParser(tokens);
}
static CommonTokenStream getTokenStream(String s) {
FlumeShellLexer lexer = new FlumeShellLexer(new ANTLRStringStream(s));
CommonTokenStream tokens = new CommonTokenStream(lexer);
return tokens;
}
static String toString(CommonTree literal) {
ASTNODE type = ASTNODE.valueOf(literal.getText());
switch (type) {
case SQUOTE:
// remove single quotes
String sq = literal.getChild(0).getText();
sq = sq.substring(1, sq.length() - 1);
return sq;
case DQUOTE:
// remove double quotes and unescape
String dq = literal.getChild(0).getText();
dq = dq.substring(1, dq.length() - 1);
return StringEscapeUtils.unescapeJava(dq);
case STRING:
// just return string
return literal.getChild(0).getText();
default:
throw new IllegalStateException("illegal parse!");
}
}
/**
* This takes a single string and parses it into a Command.
*/
public static Command parseLine(String s) throws RecognitionException {
try {
CommonTree cmd = (CommonTree) getShellCmdParser(s).line().getTree();
CommonTree ast = (CommonTree) cmd.getChild(0);
String command = toString(ast);
cmd.deleteChild(0);
ArrayList<String> lst = new ArrayList<String>();
for (int i = 0; i < cmd.getChildCount(); i++) {
String tok = toString((CommonTree) cmd.getChild(i));
lst.add(tok);
}
return new Command(command, lst.toArray(new String[0]));
} catch (RecognitionException e) {
LOG.debug("Failed to parse line, '" + s + "' because of "
+ e.getMessage(), e);
throw e;
} catch (RuntimeException rex) {
// right now lexer errors are RTE's
LOG
.debug("Failed to lex '" + s + "' because of " + rex.getMessage(),
rex);
throw new CommandLineException(rex);
}
}
}