package edu.uw.zookeeper.client.cli;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Iterator;
import java.util.LinkedList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.Lists;
import edu.uw.zookeeper.common.Pair;
public class TokenParser implements Function<CharSequence, Iterator<Pair<Integer, String>>> {
public static TokenParser defaults() {
return new TokenParser(SPACE);
}
protected static char SINGLE_QUOTE_CHAR = '\'';
protected static char DOUBLE_QUOTE_CHAR = '"';
protected static char ESCAPE_CHAR = '\\';
protected static Character SPACE = Character.valueOf(' ');
protected final Logger logger = LogManager.getLogger(getClass());
protected final Pair<Character, CharMatcher> delimiter;
public TokenParser(Character delimit) {
this.delimiter = Pair.create(delimit, CharMatcher.is(delimit));
}
public Pair<Character, CharMatcher> delimiter() {
return delimiter;
}
@Override
public TokenIterator apply(CharSequence input) {
return new TokenIterator(input);
}
protected static enum State {
START,
TOKEN,
SINGLE_QUOTED,
DOUBLE_QUOTED,
ESCAPED;
}
public class TokenIterator extends AbstractIterator<Pair<Integer, String>> {
protected final CharSequence input;
protected int index;
public TokenIterator(CharSequence input) {
this.input = checkNotNull(input);
this.index = 0;
}
public CharSequence getInput() {
return input;
}
public int getIndex() {
return index;
}
@Override
protected Pair<Integer, String> computeNext() {
logger.entry(input, index);
Pair<Integer, String> token = null;
StringBuilder tokenBuilder = new StringBuilder();
Integer tokenStart = null;
LinkedList<State> states = Lists.newLinkedList();
states.push(State.START);
while ((token == null) && (index <= input.length())) {
if (index < input.length()) {
char c = input.charAt(index);
if (delimiter.second().matches(c)) {
switch (states.peek()) {
case START:
if (tokenBuilder.length() > 0) {
token = Pair.create(tokenStart, tokenBuilder.toString());
}
break;
case TOKEN:
token = Pair.create(tokenStart, tokenBuilder.toString());
states.pop();
break;
case SINGLE_QUOTED:
case DOUBLE_QUOTED:
tokenBuilder.append(c);
break;
case ESCAPED:
tokenBuilder.append(c);
states.pop();
break;
}
} else if (SINGLE_QUOTE_CHAR == c) {
switch (states.peek()) {
case START:
states.push(State.SINGLE_QUOTED);
tokenStart = index;
break;
case TOKEN:
throw new IllegalArgumentException("Embedded quote must be escaped");
case SINGLE_QUOTED:
token = Pair.create(tokenStart, tokenBuilder.toString());
states.pop();
break;
case DOUBLE_QUOTED:
tokenBuilder.append(c);
break;
case ESCAPED:
tokenBuilder.append(c);
states.pop();
break;
}
} else if (DOUBLE_QUOTE_CHAR == c) {
switch (states.peek()) {
case START:
states.push(State.DOUBLE_QUOTED);
tokenStart = index;
break;
case TOKEN:
throw new IllegalArgumentException("Embedded quote must be escaped");
case SINGLE_QUOTED:
tokenBuilder.append(c);
break;
case DOUBLE_QUOTED:
token = Pair.create(tokenStart, tokenBuilder.toString());
states.pop();
break;
case ESCAPED:
tokenBuilder.append(c);
states.pop();
break;
}
} else if (ESCAPE_CHAR == c) {
switch (states.peek()) {
case START:
case TOKEN:
case DOUBLE_QUOTED:
case SINGLE_QUOTED:
states.push(State.ESCAPED);
tokenStart = index;
break;
case ESCAPED:
tokenBuilder.append(c);
states.pop();
break;
}
} else {
switch (states.peek()) {
case START:
tokenBuilder.append(c);
states.push(State.TOKEN);
tokenStart = index;
break;
case TOKEN:
case DOUBLE_QUOTED:
case SINGLE_QUOTED:
tokenBuilder.append(c);
break;
case ESCAPED:
tokenBuilder.append(c);
states.pop();
break;
}
}
} else {
switch (states.peek()) {
case DOUBLE_QUOTED:
case SINGLE_QUOTED:
throw new IllegalArgumentException("Incomplete quote");
case ESCAPED:
throw new IllegalArgumentException("Incomplete escape");
default:
// complete the last token
if (tokenBuilder.length() > 0) {
token = Pair.create(tokenStart, tokenBuilder.toString());
}
break;
}
}
index++;
}
if (token == null) {
return logger.exit(endOfData());
} else {
return logger.exit(token);
}
}
}
}