package org.cryptocoinpartners.command;
import com.google.inject.Binder;
import com.google.inject.Module;
import com.google.inject.Provider;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.apache.commons.lang.StringUtils;
import org.cryptocoinpartners.util.ConfigUtil;
import org.cryptocoinpartners.util.Injector;
import org.cryptocoinpartners.util.ReflectionUtil;
import javax.inject.Inject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
/**
* @author Tim Olson
*/
public abstract class AntlrCommandBase extends CommandBase {
@Override
public abstract String getUsageHelp();
@Override
public void parse( String commandArguments )
{
initCommandArgs();
Lexer lexer = (Lexer) ReflectionUtil.instantiateClassByName(grammarPath + "Lexer",
new Object[]{new ANTLRInputStream(commandArguments)},
new Class[]{CharStream.class});
CommonTokenStream tokens = new CommonTokenStream(lexer);
Parser parser = (Parser) ReflectionUtil.instantiateClassByName(grammarPath + "Parser",
new Object[]{tokens},
new Class[]{TokenStream.class});
initParseTreeListener();
lexer.setInputStream(new ANTLRInputStream(commandArguments));
Method argsMethod;
try {
argsMethod = parser.getClass().getMethod("args");
}
catch( NoSuchMethodException e ) {
throw new Error("Your Antlr grammar must name the starting node \"args\"");
}
ParseTree tree;
try {
tree = (ParseTree) argsMethod.invoke(parser);
}
catch( InvocationTargetException | IllegalAccessException e ) {
throw new Error("Could not invoke the args() method of "+parser.getClass().getName(),e);
}
catch( ClassCastException e ) {
throw new Error("The args() method on "+parser.getClass().getName()+" must return an instance of type "+ParseTree.class.getName(),e);
}
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(listener, tree);
}
/**
* this is called before each parse and should contain any initialization you need for your command options
* before the ParseTreeWalker is invoked.
*/
protected void initCommandArgs() {}
@SuppressWarnings("UnusedDeclaration")
protected AntlrCommandBase() {
autoSetGrammarPath();
}
@SuppressWarnings("UnusedDeclaration")
protected AntlrCommandBase(String grammarPath) {
this.grammarPath = grammarPath;
}
private void autoSetGrammarPath() {
Class<? extends AntlrCommandBase> cls = getClass();
autoSetGrammarPath(cls);
}
private void autoSetGrammarPath(Class<? extends AntlrCommandBase> cls) {
String name = cls.getSimpleName();
if( !name.endsWith("Command") ) {
throw new Error("If the name of your subclass of AntlrCommandBase doesn't end with \"Command\" then you need to use the AntlrCommandBase(Parser) constructor to pass in your command's Antlr Parser");
}
String baseClassName = name.substring(0,name.length() - "Command".length());
grammarPath = cls.getPackage().getName() + "." + baseClassName;
}
@SuppressWarnings("unchecked")
private void initParseTreeListener() {
// to create the grammar's generated Listener we need to first find the Antlr generated class, then we look for
// the cointrader subclass beneath the Antlr listener.
String listenerClassName = grammarPath + "BaseListener";
Class<? extends ParseTreeListener> listenerSubclass;
Class listenerBaseClass = ReflectionUtil.classForName(listenerClassName);
Set listenerSubtypes = ReflectionUtil.getCommandReflections().getSubTypesOf(listenerBaseClass);
if( listenerSubtypes.size() > 1 )
throw new Error("Found multiple subclasses of "+listenerClassName+":\n"+StringUtils.join(listenerSubtypes,',')+"\n. Use the explicit AntlrCommandBase(String,Lexer,Parser,ParseTreeListener) constructor.");
if( listenerSubtypes.isEmpty() )
throw new Error("Could not find any subclass of "+listenerClassName+" in the command.path "+ ConfigUtil
.combined().getString("command.path"));
listenerSubclass = (Class<? extends ParseTreeListener>) listenerSubtypes.iterator().next();
Injector listenerInjector = injector.createChildInjector(new Module() {
public void configure(Binder binder) {
final Provider selfProvider = new SelfProvider();
binder.bind(Command.class).toProvider(selfProvider);
binder.bind(AntlrCommandBase.this.getClass()).toProvider(selfProvider);
}
});
listenerInjector = getListenerInjector(listenerInjector);
listener = listenerInjector.getInstance(listenerSubclass);
}
protected Injector getListenerInjector(Injector parentInjector) { return parentInjector; }
protected void setListener(ParseTreeListener listener) { this.listener = listener; }
private class SelfProvider implements Provider<AntlrCommandBase> {
@Override
public AntlrCommandBase get() {
return AntlrCommandBase.this;
}
}
@Inject
private Injector injector;
private ParseTreeListener listener;
private String grammarPath; // e.g. org.cryptocoinpartners.command.Order Suffixes like "Parser" and "Lexer" will be appended
}