/**
* MIT License
*
* Copyright (c) 2017 zgqq
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package mah.command;
import lombok.val;
import mah.command.event.CommonFilterEvent;
import mah.command.event.NotFoundCommandEvent;
import mah.command.event.TriggerEvent;
import mah.event.ComparableEventHandler;
import mah.event.EventHandler;
import mah.ui.input.InputTextChangedEvent;
import mah.ui.input.TextState;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by zgq on 2/13/17.
*/
public class CommandExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(CommandExecutor.class);
private final ExecutorService executorService = Executors.newCachedThreadPool();
private final Map<String, Command> commands;
private final List<ComparableEventHandler<NotFoundCommandEvent>> notFoundCommandHandlers = new ArrayList<>();
private final List<CommandPostProcessor> commandPostProcessors = new ArrayList<>();
private Command currentCommand;
private String currentTriggerKey;
private String currentQuery;
CommandExecutor(Map<String, Command> commands) {
this.commands = commands;
}
private static final Comparator COMPARATOR = (Comparator<ComparableEventHandler<NotFoundCommandEvent>>)
(o1, o2) -> o1.compareTo(o2);
void addCommandPostProcessor(CommandPostProcessor postProccessor) {
commandPostProcessors.add(postProccessor);
}
void addNotFoundCommandHandler(ComparableEventHandler<NotFoundCommandEvent> commandEventEventHandler) {
this.notFoundCommandHandlers.add(commandEventEventHandler);
this.notFoundCommandHandlers.sort(COMPARATOR);
}
@Nullable
Command getCurrentCommand() {
return currentCommand;
}
@Nullable
String getCurrentTriggerKey() {
return currentTriggerKey;
}
String getCurrentQuery() {
return currentQuery;
}
void shutdownNow() {
executorService.shutdownNow();
}
void tryTriggerCommand(InputTextChangedEvent event) {
TextState newState = event.getNewState();
String input = newState.getText();
try {
boolean triggerSucc = false;
for (Map.Entry<String, Command> entry : commands.entrySet()) {
String key = entry.getKey();
Command command = entry.getValue();
if (input.startsWith(key)) {
triggerCommand(command, key, input);
triggerSucc = true;
int index;
if (input.equals(key)) {
index = key.length();
currentTriggerKey = key;
} else {
index = key.length() + 1;
currentTriggerKey = key + ' ';
}
currentQuery = input.substring(index);
}
}
if (!triggerSucc) {
handleNotFoundCommand(input);
}
} catch (Exception e) {
throw new CommandException(e);
}
}
private void handleNotFoundCommand(String input) throws Exception {
// There is no command found
if (currentCommand != null) {
currentCommand.idle();
}
NotFoundCommandEvent notFoundCommandEvent = new NotFoundCommandEvent(input, currentCommand);
for (EventHandler<NotFoundCommandEvent> notFoundCommandHandler : notFoundCommandHandlers) {
notFoundCommandHandler.handle(notFoundCommandEvent);
}
if (currentCommand != null) {
currentCommand = null;
}
currentTriggerKey = null;
currentQuery = null;
}
private ExecutionContext buildExecutionContext(Command command, String input) {
return new ExecutionContext(command, input);
}
private void beforeExecute(ExecutionContext context) {
for (CommandPostProcessor commandPostProcessor : commandPostProcessors) {
commandPostProcessor.postProcessBeforeExecute(context);
}
}
private void afterExecute(ExecutionContext context) {
for (CommandPostProcessor commandPostProcessor : commandPostProcessors) {
commandPostProcessor.postProcessAfterExecute(context);
}
}
private void triggerCommand(Command command, String triggerKey, String input) throws Exception {
ExecutionContext executionContext = buildExecutionContext(command, input);
if (input.equals(triggerKey)) {
beforeExecute(executionContext);
executeCommand(command, triggerKey);
afterExecute(executionContext);
currentCommand = command;
return;
} else {
if (input.charAt(triggerKey.length()) == ' ') {
beforeExecute(executionContext);
filterCommand(command, triggerKey, input);
afterExecute(executionContext);
currentCommand = command;
return;
}
}
}
private void executeCommand(Command command, String triggerKey) {
executorService.submit(new TriggerCommandTask(command, triggerKey));
}
private void filterCommand(Command command, String triggerKey, String input) throws Exception {
String inputContent = input.substring(triggerKey.length() + 1, input.length());
executorService.submit(new FilterCommandTask(command, triggerKey, inputContent));
}
static class TriggerCommandTask implements Runnable {
private final Command command;
private final String triggerKey;
TriggerCommandTask(Command command, String triggerKey) {
this.command = command;
this.triggerKey = triggerKey;
}
@Override
public void run() {
try {
List<EventHandler<? extends TriggerEvent>> triggerEventHandlers = command.getTriggerEventHandlers();
TriggerEvent triggerEvent = new TriggerEvent(triggerKey);
for (EventHandler triggerEventHandler : triggerEventHandlers) {
triggerEventHandler.handle(triggerEvent);
}
} catch (Throwable e) {
LOGGER.error("Command " + command + " failed to be executed", e);
}
}
}
static class FilterCommandTask implements Runnable {
private final Command command;
private final String triggerKey;
private final String content;
FilterCommandTask(Command command, String triggerKey, String content) {
this.command = command;
this.triggerKey = triggerKey;
this.content = content;
}
@Override
public void run() {
try {
CommonFilterEvent commonFilterEvent = new CommonFilterEvent(triggerKey, content);
List<EventHandler<? extends CommonFilterEvent>> commonFilterEventHandlers = command
.getCommonFilterEventHandlers();
for (EventHandler commonFilterEventHandler : commonFilterEventHandlers) {
commonFilterEventHandler.handle(commonFilterEvent);
}
} catch (Throwable e) {
LOGGER.error("Command " + this.command + " failed to be filtered", e);
}
}
}
}