package edu.uw.zookeeper.client.cli;
import static com.google.common.base.Preconditions.checkArgument;
import java.lang.reflect.AnnotatedElement;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import com.google.common.base.CaseFormat;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.MapMaker;
import edu.uw.zookeeper.common.Pair;
import edu.uw.zookeeper.data.ZNodeLabelVector;
import edu.uw.zookeeper.data.ZNodeName;
import edu.uw.zookeeper.data.ZNodePath;
public class CommandParser implements Function<Iterable<String>, Invocation<?>> {
public static CommandParser empty(Environment environment) {
return new CommandParser(environment,
new MapMaker().<CharSequence, Pair<CommandDescriptor, Object>>makeMap());
}
protected static CommandDescriptor getCommandDescriptor(Object command) {
try {
CommandDescriptor descriptor = null;
if (command instanceof AnnotatedElement) {
descriptor = ((AnnotatedElement) command).getAnnotation(CommandDescriptor.class);
}
if (descriptor == null) {
if (command instanceof Enum) {
descriptor = getCommandDescriptor(command.getClass().getField(((Enum<?>) command).name()));
} else {
descriptor = getCommandDescriptor(command.getClass());
}
}
return descriptor;
} catch (Exception e) {
throw new IllegalArgumentException(String.valueOf(command), e);
}
}
protected static Iterable<String> getCommandNames(Pair<CommandDescriptor, Object> descriptor) {
Iterable<String> names;
if (descriptor.first().names().length > 0) {
names = Arrays.asList(descriptor.first().names());
} else {
names = ImmutableList.of(CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, ((Enum<?>) descriptor.second()).name()));
}
return names;
}
protected final Logger logger = LogManager.getLogger(getClass());
protected final Environment environment;
protected final Map<CharSequence, Pair<CommandDescriptor, Object>> commands;
protected CommandParser(
Environment environment,
Map<CharSequence, Pair<CommandDescriptor, Object>> commands) {
this.environment = environment;
this.commands = commands;
}
@SuppressWarnings("unchecked")
public <T> Pair<CommandDescriptor, T> getCommand(CharSequence name) {
Pair<CommandDescriptor, Object> descriptor = commands.get(name);
if (descriptor != null) {
return (Pair<CommandDescriptor, T>) descriptor;
} else {
return null;
}
}
public CommandParser withCommand(Object command) {
Pair<CommandDescriptor, Object> descriptor = Pair.create(getCommandDescriptor(command), command);
for (String name: getCommandNames(descriptor)) {
logger.debug("Adding command {} => {}", name, command);
checkArgument(! commands.containsKey(name));
commands.put(name, descriptor);
}
return this;
}
public Set<Map.Entry<CharSequence, Pair<CommandDescriptor, Object>>> getCommands() {
return commands.entrySet();
}
@Override
public Invocation<Object> apply(Iterable<String> tokens) {
String name = Iterables.getFirst(tokens, null);
if (name == null) {
return null;
}
Pair<CommandDescriptor, Object> command = getCommand(name);
checkArgument(command != null, String.format(
"Not a command: '%s'", name));
ArgumentDescriptor[] ads = command.first().arguments();
Object[] arguments = new Object[ads.length + 1];
Iterator<String> itr = tokens.iterator();
checkArgument(itr.hasNext());
arguments[0] = itr.next();
for (int i=0; i<ads.length; ++i) {
ArgumentDescriptor ad = ads[i];
String token;
if (itr.hasNext()) {
token = itr.next();
} else {
token = ad.value();
}
Object argument = null;
switch (ad.token()) {
case ENUM:
for (Object e: ad.type().getEnumConstants()) {
if (e.toString().equals(token)) {
argument = e;
break;
}
}
break;
case STRING:
argument = token;
break;
case PATH:
if (token.isEmpty()) {
argument = environment.get(ShellInvoker.CWD_KEY);
} else if (token.charAt(0) == ZNodeName.SLASH) {
argument = ZNodePath.validated(token);
} else {
argument = ZNodePath.canonicalized(ZNodeLabelVector.join(environment.get(ShellInvoker.CWD_KEY).toString(), token));
}
break;
case INTEGER:
argument = Integer.valueOf(token);
break;
default:
argument = null;
}
checkArgument(argument != null, String.format("Error parsing argument #%d: %s", i+1, token));
arguments[i+1] = argument;
}
checkArgument(! itr.hasNext(), String.format("Extra arguments after #%d", ads.length));
return Invocation.create(command, arguments);
}
}