package edu.uw.zookeeper.client.cli;
import static com.google.common.base.Preconditions.checkArgument;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import jline.console.completer.Completer;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.AbstractExecutionThreadService;
import edu.uw.zookeeper.client.ClientExecutor;
import edu.uw.zookeeper.common.Pair;
import edu.uw.zookeeper.data.AbsoluteZNodePath;
import edu.uw.zookeeper.data.ZNodePath;
import edu.uw.zookeeper.data.Operations;
import edu.uw.zookeeper.data.ZNodeLabelVector;
import edu.uw.zookeeper.data.ZNodeName;
import edu.uw.zookeeper.protocol.Message;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.proto.Records;
public class DispatchingInvoker extends AbstractExecutionThreadService implements Invoker<Object>, Completer {
@Invokes(commands={Object.class})
public static DispatchingInvoker defaults(Shell shell, Class<?>...types) {
Class<?>[] withDefaults = new Class<?>[types.length + 3];
withDefaults[0] = ShellInvoker.class;
withDefaults[1] = ClientExecutorInvoker.class;
withDefaults[2] = RmrInvoker.class;
for (int i=0; i<types.length; ++i) {
withDefaults[i+3] = types[i];
}
return forInvokers(shell, withDefaults);
}
public static DispatchingInvoker forInvokers(Shell shell, Class<?>...types) {
ImmutableMap.Builder<Class<?>, Pair<Invokes, Invoker<Object>>> invokers = ImmutableMap.builder();
for (Class<?> type: types) {
Pair<Invokes, Invoker<Object>> invoker = getInvoker(shell, type);
for (Class<?> c: invoker.first().commands()) {
invokers.put(c, invoker);
}
}
return new DispatchingInvoker(shell, invokers.build());
}
@SuppressWarnings("unchecked")
protected static Pair<Invokes, Invoker<Object>> getInvoker(Shell shell, Class<?> cls) {
try {
for (Method m: cls.getDeclaredMethods()) {
Invokes invokes = m.getAnnotation(Invokes.class);
if (invokes == null) {
continue;
}
Invoker<Object> invoker;
if (Modifier.isStatic(m.getModifiers())) {
try {
Object[] args;
if (m.getParameterTypes().length > 0) {
args = new Object[1];
args[0] = shell;
} else {
args = new Object[0];
}
invoker = (Invoker<Object>) m.invoke(null, args);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InvocationTargetException e) {
throw new AssertionError(e);
}
} else {
throw new IllegalArgumentException(m.toString());
}
return Pair.create(invokes, invoker);
}
} catch (Exception e) {
throw new IllegalArgumentException(String.valueOf(cls), e);
}
return null;
}
protected final Logger logger = LogManager.getLogger(getClass());
protected final Shell shell;
protected final Map<Class<?>, Pair<Invokes, Invoker<Object>>> invokers;
protected DispatchingInvoker(
Shell shell,
Map<Class<?>, Pair<Invokes, Invoker<Object>>> invokers) {
this.shell = shell;
this.invokers = invokers;
}
@Override
public void invoke(Invocation<Object> input)
throws Exception {
Invoker<Object> invoker = invokers.get(input.getCommand().second().getClass()).second();
checkArgument(invoker != null, input.getCommand());
invoker.invoke(input);
}
@Override
public int complete(String buffer, int cursor,
List<CharSequence> candidates) {
logger.entry(buffer, cursor, candidates);
List<Pair<Integer, String>> tokens;
if (buffer == null) {
tokens = ImmutableList.of();
} else {
tokens = ImmutableList.copyOf(shell.getTokenizer().apply(buffer));
}
int argIndex = -1;
int ret;
String name;
Pair<CommandDescriptor, Object> command;
if (tokens.isEmpty()) {
ret = cursor;
name = "";
command = null;
} else {
Pair<Integer, String> token = tokens.get(0);
ret = token.first();
name = token.second();
command = shell.getCommands().getCommand(name);
}
ImmutableSortedSet.Builder<String> builder = ImmutableSortedSet.naturalOrder();
if (command == null) {
for (Map.Entry<CharSequence, Pair<CommandDescriptor, Object>> e : shell.getCommands().getCommands()) {
String candidate = String.valueOf(e.getKey());
if (candidate.startsWith(name)) {
builder.add(candidate);
command = e.getValue();
}
}
} else {
String token;
if (shell.getTokenizer().delimiter().second().matches(buffer.charAt(cursor - 1))) {
argIndex = tokens.size() - 1;
ret = cursor;
token = "";
} else {
argIndex = tokens.size() - 2;
ret = tokens.get(argIndex + 1).first();
token = tokens.get(argIndex + 1).second();
}
ArgumentDescriptor[] arguments = command.first().arguments();
if ((argIndex < 0) || (argIndex >= arguments.length)) {
return logger.exit(-1);
}
ArgumentDescriptor ad = arguments[argIndex];
switch (ad.token()) {
case ENUM:
for (Object e: ad.type().getEnumConstants()) {
String value = e.toString();
if (value.startsWith(token)) {
builder.add(value);
}
}
break;
case PATH:
{
ClientExecutor<Operation.Request, ? extends Message.ServerResponse<?>, ?> client = shell.getEnvironment().get(ClientExecutorInvoker.CLIENT_KEY).getConnectionClientExecutor();
ZNodePath path = ZNodePath.canonicalized(ZNodeLabelVector.join(shell.getEnvironment().get(ShellInvoker.CWD_KEY).toString(), token));
try {
Operations.Requests.GetChildren request = Operations.Requests.getChildren();
if (token.isEmpty() || token.endsWith(Character.toString(ZNodeName.SLASH))) {
request.setPath(path);
} else {
request.setPath(((AbsoluteZNodePath) path).parent());
}
Message.ServerResponse<?> response = client.submit(request.build()).get();
logger.trace("{} {} {}", path, token, response);
if (response.record() instanceof Records.ChildrenGetter) {
if (token.isEmpty() || token.endsWith(Character.toString(ZNodeName.SLASH))) {
ret = cursor;
builder.addAll(((Records.ChildrenGetter) response.record()).getChildren());
} else {
int lastSlash = token.lastIndexOf(ZNodeName.SLASH);
String prefix;
if (lastSlash >= 0) {
ret += lastSlash + 1;
prefix = token.substring(lastSlash + 1);
} else {
prefix = token;
}
for (String child: ((Records.ChildrenGetter) response.record()).getChildren()) {
if (child.startsWith(prefix)) {
builder.add(child);
}
}
}
}
} catch (Exception e) {
throw Throwables.propagate(e);
}
break;
}
default:
break;
}
}
ImmutableSortedSet<String> values = builder.build();
if (values.isEmpty()) {
return logger.exit(-1);
}
if ((values.size() == 1) && (command != null) && (command.first().arguments().length > argIndex + 1)) {
values = ImmutableSortedSet.of(Iterables.getOnlyElement(values) + shell.getTokenizer().delimiter().first());
}
candidates.addAll(values);
logger.trace("{}", values);
return logger.exit(ret);
}
@Override
protected void startUp() throws Exception {
for (Pair<Invokes, Invoker<Object>> invoker: invokers.values()) {
shell.getRuntime().getServiceMonitor().addOnStart(invoker.second());
if (! invoker.second().isRunning()) {
invoker.second().startAsync().awaitRunning();
}
}
shell.getReader().addCompleter(this);
}
@Override
protected void run() throws Exception {
String line = null;
while (isRunning() && shell.isRunning() && ((line = shell.readLine()) != null)) {
try {
TokenParser.TokenIterator itr = shell.getTokenizer().apply(line);
ImmutableList.Builder<String> tokens = ImmutableList.builder();
while (itr.hasNext()) {
tokens.add(itr.next().second());
}
Invocation<Object> invocation = shell.getCommands().apply(tokens.build());
if (invocation == null) {
continue;
}
invoke(invocation);
} catch (IllegalArgumentException e) {
shell.printThrowable(e);
continue;
}
}
}
@Override
protected void shutDown() throws Exception {
}
}