Skip to content
This repository has been archived by the owner on Apr 16, 2023. It is now read-only.

Commit

Permalink
Clean up CLI-handling by creating a new CLIService class
Browse files Browse the repository at this point in the history
Closes: #21
  • Loading branch information
MarkL4YG committed Feb 6, 2020
1 parent feb8261 commit 083ef8b
Show file tree
Hide file tree
Showing 8 changed files with 259 additions and 208 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ dependencies {
compile 'com.fasterxml.jackson.core:jackson-core:2.10.2'
compile 'com.fasterxml.jackson.core:jackson-databind:2.10.2'
compile 'com.fasterxml.jackson.core:jackson-annotations:2.10.2'
compile 'org.antlr:antlr4:4.8-1'
antlr 'org.antlr:antlr4:4.8-1'
compile 'org.antlr:antlr4:4.7.1'
antlr 'org.antlr:antlr4:4.7.1'

runtime 'org.antlr:antlr4-runtime:4.8-1'
runtime 'org.antlr:antlr4-runtime:4.7.1'
runtime 'org.apache.logging.log4j:log4j-api:2.13.0'
runtime 'org.apache.logging.log4j:log4j-core:2.13.0'
runtime 'org.apache.logging.log4j:log4j-slf4j-impl:2.13.0'
Expand Down
100 changes: 78 additions & 22 deletions src/main/java/de/fearnixx/jeak/Main.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.fearnixx.jeak;

import de.fearnixx.jeak.commandline.CommandLine;
import de.fearnixx.jeak.commandline.CLIService;
import de.fearnixx.jeak.plugin.persistent.PluginManager;
import de.fearnixx.jeak.reflect.JeakBotPlugin;
import de.fearnixx.jeak.test.AbstractTestPlugin;
Expand All @@ -15,11 +15,12 @@

import java.io.File;
import java.lang.management.ManagementFactory;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class Main implements Runnable {

Expand All @@ -32,7 +33,7 @@ public class Main implements Runnable {
private final Executor mainExecutor = Executors.newSingleThreadExecutor(
new NamePatternThreadFactory("main-%d"));
private final JeakBot jeakBot = new JeakBot();
private final CommandLine cmd = new CommandLine(System.in, System.out);
private boolean cliTerminated = false;

public static void main(String[] arguments) {
for (String arg : arguments) {
Expand Down Expand Up @@ -93,12 +94,52 @@ public static Main getInstance() {
}

public void run() {
discoverPlugins();
populatePluginSources();
startBot();
runLoop();

CLIService cliService = CLIService.getInstance();
cliService.registerCommand("stop", ctx -> {
ctx.getMessageConsumer().accept("Bye bye!");
synchronized (this) {
cliTerminated = true;
jeakBot.shutdown();
}
});
cliService.registerCommand("sysInfo", ctx -> {
dumpSysInfo(ctx.getMessageConsumer());
});

ExecutorService execSvc = Executors.newSingleThreadExecutor();



try (Scanner sysInScanner = new Scanner(System.in)) {
Supplier<Future<String>> next = () -> execSvc.submit(sysInScanner::nextLine);
Future<String> futConsoleLine = next.get();
while (true) {
synchronized (this) {
if (cliTerminated) break;
}

try {
String line = futConsoleLine.get(1, TimeUnit.SECONDS);
CLIService.getInstance().receiveLine(line);
futConsoleLine = next.get();

} catch (InterruptedException e) {
logger.warn("Interrupted while reading system in? Shutting down...");
jeakBot.shutdown();
} catch (ExecutionException e) {
logger.error("PANIC! Failed to read from system in!", e);
jeakBot.shutdown();
} catch (TimeoutException e) {
// We're just going to ignore this ^^. Probably the admin doesn't want to talk with us anyways.
}
}
}
}

private void discoverPlugins() {
private void populatePluginSources() {
pluginManager.addSource(new File("plugins"));
pluginManager.addSource(new File("libraries"));
}
Expand All @@ -114,9 +155,7 @@ private void startBot() {
jeakBot.setConfig(botConfig);
jeakBot.setPluginManager(pluginManager);


jeakBot.onShutdown(this::onBotShutdown);

jeakBot.onShutdown(bot -> internalShutdown());
mainExecutor.execute(jeakBot);
}

Expand All @@ -126,21 +165,13 @@ private IConfig createConfig(File confFile) {
return new FileConfig(configLoader, confFile);
}

private void runLoop() {
cmd.run();
}

private void onBotShutdown(JeakBot bot) {
internalShutdown();
}

public void shutdown() {
jeakBot.shutdown();
internalShutdown();
}

private void internalShutdown() {
cmd.kill();
cliTerminated = true;
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
Expand Down Expand Up @@ -181,4 +212,29 @@ private void internalShutdown() {

System.exit(0);
}

public void dumpSysInfo(Consumer<String> acceptor) {
final Consumer<String> dump = str -> {
acceptor.accept(str);
logger.info("[SysInfo] {}", str);
};

try {
var netIfIterator = NetworkInterface.getNetworkInterfaces().asIterator();
while (netIfIterator.hasNext()) {
NetworkInterface netIf = netIfIterator.next();
dump.accept(String.format("[IF] Name=%s MAC=%s", netIf.getDisplayName(), Arrays.toString(netIf.getHardwareAddress())));
for (InterfaceAddress ifAddr : netIf.getInterfaceAddresses()) {
String addrStr = Arrays.toString(ifAddr.getAddress().getAddress());
String prefix = Integer.toString(ifAddr.getNetworkPrefixLength());
String hostname = ifAddr.getAddress().getHostName();
String broadcast = Arrays.toString(ifAddr.getBroadcast().getAddress());
dump.accept(String.format("IP: %s/%s [%s] BR: %s", addrStr, prefix, hostname, broadcast));
}
}

} catch (Exception e) {
logger.error("Could not retrieve system information!", e);
}
}
}
60 changes: 60 additions & 0 deletions src/main/java/de/fearnixx/jeak/antlr/CommandParserUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package de.fearnixx.jeak.antlr;

import de.fearnixx.jeak.service.command.CommandCtxVisitor;
import de.fearnixx.jeak.service.command.CommandInfo;
import de.fearnixx.jeak.service.command.SyntaxErrorListener;
import de.mlessmann.confort.lang.RuntimeParseException;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CodePointCharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.slf4j.Logger;

public abstract class CommandParserUtil {

private CommandParserUtil() {
}

public static CommandInfo parseCommandLine(String arguments, Logger logger) {
CodePointCharStream charStream = CharStreams.fromString(arguments);
var lexer = new CommandExecutionCtxLexer(charStream);
var tokenStream = new CommonTokenStream(lexer);
var parser = new CommandExecutionCtxParser(tokenStream);


// Use 2-stage parsing for expression performance
// https://github.com/antlr/antlr4/blob/master/doc/faq/general.md#why-is-my-expression-parser-slow
try {
// STAGE 1
var treeVisitor = new CommandCtxVisitor();
var errorListener = new SyntaxErrorListener(treeVisitor.getInfo().getErrorMessages()::add);

logger.debug("Trying to run STAGE 1 parsing. (SSL prediction)");
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
parser.removeErrorListeners();
parser.addErrorListener(errorListener);
var grammarContext = parser.commandExecution();
treeVisitor.visitCommandExecution(grammarContext);
return treeVisitor.getInfo();
} catch (Exception ex) {
// STAGE 2
var treeVisitor = new CommandCtxVisitor();
var errorListener = new SyntaxErrorListener(treeVisitor.getInfo().getErrorMessages()::add);

logger.debug("Trying to run STAGE 2 parsing. (LL prediction)", ex);
tokenStream.seek(0);
parser.reset();
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
parser.removeErrorListeners();
parser.addErrorListener(errorListener);

try {
var grammarContext = parser.commandExecution();
treeVisitor.visitCommandExecution(grammarContext);
} catch (RuntimeParseException e) {
treeVisitor.getInfo().getErrorMessages().add(e.getMessage());
}
return treeVisitor.getInfo();
}
}
}
24 changes: 24 additions & 0 deletions src/main/java/de/fearnixx/jeak/commandline/CLICommandContext.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package de.fearnixx.jeak.commandline;

import de.fearnixx.jeak.service.command.CommandInfo;

import java.util.function.Consumer;

public class CLICommandContext {

private final CommandInfo commInfo;
private final Consumer<String> messageConsumer;

public CLICommandContext(CommandInfo commInfo, Consumer<String> messageConsumer) {
this.commInfo = commInfo;
this.messageConsumer = messageConsumer;
}

public CommandInfo getCommandInfo() {
return commInfo;
}

public Consumer<String> getMessageConsumer() {
return messageConsumer;
}
}
81 changes: 81 additions & 0 deletions src/main/java/de/fearnixx/jeak/commandline/CLIService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package de.fearnixx.jeak.commandline;

import de.fearnixx.jeak.antlr.CommandParserUtil;
import de.fearnixx.jeak.service.command.CommandInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.OutputStream;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;

import static de.fearnixx.jeak.antlr.CommandParserUtil.parseCommandLine;

public class CLIService {

private static final Object INSTANCE_LOCK = new Object();
private static final Logger logger = LoggerFactory.getLogger(CLIService.getInstance().getClass());
public static CLIService instance;

public synchronized static CLIService getInstance() {
synchronized (INSTANCE_LOCK) {
if (instance == null) {
instance = new CLIService();
}
return instance;
}
}

/**
* In case the framework is not started via its shipped main class, calling applications may override
* the cli-command service instance so they can properly receive the command registrations.
* @implNote This <em>MUST</em> be called before any interaction with the actual framework as the instance probably will be initialized by then.
*/
protected static void setInstance(CLIService service) {
synchronized (INSTANCE_LOCK) {
if (instance != null) {
throw new IllegalStateException("Replacement CLI-Services MUST be set before the first #getInstance call!");
}
instance = service;
}
}

protected final Map<String, Consumer<CLICommandContext>> cliCommands = new ConcurrentHashMap<>();

protected CLIService() {
}

protected Consumer<String> getMessageConsumer() {
return System.out::println;
}

public Optional<Consumer<CLICommandContext>> registerCommand(String command, Consumer<CLICommandContext> commandConsumer) {
return Optional.ofNullable(cliCommands.put(command, commandConsumer));
}

public void receiveLine(String input) {
int spacePos = input.indexOf(' ');
String command;
String contextPart = null;
if (spacePos < 0) {
command = input;
} else {
command = input.substring(0, spacePos);
contextPart = input.substring(spacePos).trim();
}

Consumer<CLICommandContext> cliConsumer = cliCommands.getOrDefault(command, null);
if (cliConsumer != null) {
CommandInfo commandInfo = parseCommandLine(contextPart, logger);
CLICommandContext cliContext = new CLICommandContext(commandInfo, getMessageConsumer());
cliConsumer.accept(cliContext);

} else {
final String unknownMsg = String.format("Unknown command: \"%s\"", command);
logger.info(unknownMsg);
getMessageConsumer().accept(unknownMsg);
}
}
}
Loading

0 comments on commit 083ef8b

Please sign in to comment.