forked from SteamWar/SteamWar
217 lines
9.2 KiB
Java
217 lines
9.2 KiB
Java
/*
|
|
* This file is a part of the SteamWar software.
|
|
*
|
|
* Copyright (C) 2020 SteamWar.de-Serverteam
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package de.steamwar.command;
|
|
|
|
import de.steamwar.command.graph.NodeData;
|
|
import de.steamwar.command.graph.RootNode;
|
|
import de.steamwar.command.utils.Pair;
|
|
import de.steamwar.command.utils.Triple;
|
|
|
|
import java.lang.annotation.Annotation;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.util.*;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.Function;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import java.util.stream.Collectors;
|
|
|
|
public abstract class AbstractCommand<T> {
|
|
|
|
private static final Map<Class<? extends AbstractCommand<?>>, Map<Class<?>, Function<?, ?>>> EXECUTOR_MAPPER = new HashMap<>();
|
|
|
|
public static <E, T> void addExecutorMapper(Class<? extends AbstractCommand<E>> wrapper, Class<T> executorType, Function<E, T> mapper) {
|
|
EXECUTOR_MAPPER.computeIfAbsent(wrapper, __ -> new HashMap<>())
|
|
.putIfAbsent(executorType, mapper);
|
|
}
|
|
|
|
protected final String command;
|
|
protected final String permission;
|
|
private final Consumer<CommandLoader<T>> initializer;
|
|
protected final String[] aliases;
|
|
protected final List<String> descriptions = new ArrayList<>();
|
|
|
|
private boolean initialized = false;
|
|
protected final RootNode<T> rootNode = new RootNode<>();
|
|
|
|
private final Map<String, AbstractTypeMapper<T, ?>> localTypeMapper = new HashMap<>();
|
|
private final Map<String, AbstractTypeValidator<T, ?>> localValidators = new HashMap<>();
|
|
private final Map<String, AbstractTypeSupplier<T, ?>> localSupplier = new HashMap<>();
|
|
|
|
protected AbstractCommand(String command, Consumer<CommandLoader<T>> initializer, String... aliases) {
|
|
this(command, null, initializer, aliases);
|
|
}
|
|
|
|
protected AbstractCommand(String command, String permission, Consumer<CommandLoader<T>> initializer, String... aliases) {
|
|
this.command = command;
|
|
this.permission = permission;
|
|
this.initializer = initializer;
|
|
this.aliases = aliases;
|
|
initCommand();
|
|
unregister();
|
|
register();
|
|
}
|
|
|
|
protected synchronized void initCommand() {
|
|
}
|
|
|
|
public abstract void unregister();
|
|
|
|
public abstract void register();
|
|
|
|
protected void commandSystemError(T sender, CommandFrameworkException e) {
|
|
Logger.getGlobal().log(Level.WARNING, "An unexpected error occurred", e);
|
|
}
|
|
|
|
protected void sendMessage(T sender, String message, Object[] args) {
|
|
}
|
|
|
|
public final void execute(T sender, String alias, String[] args) {
|
|
initialize();
|
|
List<Runnable> errors = new ArrayList<>();
|
|
try {
|
|
boolean executed = rootNode.execute(sender, new NodeData(alias, fixArgs(args, true), (s, args1) -> {
|
|
errors.add(() -> sendMessage(sender, s, args1));
|
|
}));
|
|
if (executed) return;
|
|
if (!errors.isEmpty()) {
|
|
errors.forEach(Runnable::run);
|
|
return;
|
|
}
|
|
descriptions.forEach(s -> sendMessage(sender, s, new Object[0]));
|
|
} catch (CommandFrameworkException e) {
|
|
commandSystemError(sender, e);
|
|
}
|
|
}
|
|
|
|
public final List<String> tabComplete(T sender, String alias, String[] args) throws IllegalArgumentException {
|
|
initialize();
|
|
List<Collection<String>> tabCompletes = new ArrayList<>();
|
|
rootNode.tabComplete(sender, new NodeData(alias, fixArgs(args, false), (s, args1) -> {}), tabCompletes);
|
|
return tabCompletes.stream()
|
|
.filter(Objects::nonNull)
|
|
.flatMap(Collection::stream)
|
|
.filter(Objects::nonNull)
|
|
.filter(s -> !s.isEmpty())
|
|
.distinct()
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
private String[] fixArgs(String[] args, boolean allowEmpty) {
|
|
int removedCount = 0;
|
|
for (int i = 0; i < args.length; i++) {
|
|
String arg = args[i];
|
|
if (arg.isEmpty() && i != args.length - 1) {
|
|
removedCount++;
|
|
continue;
|
|
}
|
|
args[i] = args[i - removedCount];
|
|
}
|
|
if (removedCount != 0) {
|
|
args = Arrays.copyOf(args, args.length - removedCount);
|
|
}
|
|
if (args.length == 0 && !allowEmpty) {
|
|
return new String[]{""};
|
|
}
|
|
return args;
|
|
}
|
|
|
|
private synchronized void initialize() {
|
|
if (initialized) return;
|
|
|
|
List<Object> commandObjects = new ArrayList<>();
|
|
initializer.accept(new CommandLoader<>() {
|
|
@Override
|
|
public AbstractCommand<T> get() {
|
|
return AbstractCommand.this;
|
|
}
|
|
|
|
@Override
|
|
public void add(Object command) {
|
|
commandObjects.add(command);
|
|
}
|
|
});
|
|
|
|
List<Pair<DataImpl, Method>> methods = new ArrayList<>();
|
|
for (Object commandObject : commandObjects) {
|
|
DataImpl dataImpl = new DataImpl(this, commandObject, rootNode, EXECUTOR_MAPPER.getOrDefault(this.getClass(), Collections.emptyMap()), (Map) localTypeMapper, CommandUtils.MAPPER_FUNCTIONS, (Map) localValidators, CommandUtils.VALIDATOR_FUNCTIONS, (Map) localSupplier, CommandUtils.SUPPLIER_FUNCTIONS);
|
|
methods(commandObject).stream()
|
|
.filter(method -> Arrays.stream(method.getAnnotations()).anyMatch(anno -> anno.annotationType().isAnnotationPresent(Handler.Implementation.class)))
|
|
.forEach(method -> methods.add(new Pair<>(dataImpl, method)));
|
|
}
|
|
|
|
Map<Integer, Map<Triple<DataImpl, Method, Annotation>, List<Handler.HandlerMethod<Annotation>>>> handlerMap = new HashMap<>();
|
|
for (Pair<DataImpl, Method> commandMethod : methods) {
|
|
List<Annotation> annotations = CommandUtils.getAnnotations(commandMethod.b);
|
|
for (Annotation annotation : annotations) {
|
|
Handler.Implementation handler = annotation.annotationType().getAnnotation(Handler.Implementation.class);
|
|
Handler handlerObject;
|
|
try {
|
|
handlerObject = handler.value().getConstructor().newInstance();
|
|
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException |
|
|
InvocationTargetException e) {
|
|
throw new UnsupportedOperationException("Handler " + handler.value().getName() + " cannot be used to check the argument validity", e);
|
|
}
|
|
if (!(handlerObject instanceof Handler.HandlerMethod)) {
|
|
throw new UnsupportedOperationException("Handler " + handlerObject.getClass().getName() + " is not a HandlerMethod");
|
|
}
|
|
handlerMap.computeIfAbsent(((Handler.HandlerMethod<?>) handlerObject).getRunPriority(), k -> new LinkedHashMap<>())
|
|
.computeIfAbsent(new Triple<>(commandMethod.a, commandMethod.b, annotation), k -> new ArrayList<>())
|
|
.add((Handler.HandlerMethod<Annotation>) handlerObject);
|
|
}
|
|
}
|
|
|
|
List<Integer> runPriorities = new ArrayList<>(handlerMap.keySet());
|
|
runPriorities.sort(Comparator.naturalOrder());
|
|
for (int runPriority : runPriorities) {
|
|
handlerMap.get(runPriority).forEach((dataMethodAnnotationTriple, handlerMethods) -> {
|
|
handlerMethods.forEach(annotationHandlerMethod -> {
|
|
try {
|
|
annotationHandlerMethod.check(new Handler.AnnotationWrapper.ForAnnotation<>(dataMethodAnnotationTriple.c), new Handler.MethodWrapper.ForMethod(dataMethodAnnotationTriple.b), dataMethodAnnotationTriple.a);
|
|
} catch (Exception e) {
|
|
throw new UnsupportedOperationException("Method check failed for " + dataMethodAnnotationTriple.b.getName(), e);
|
|
}
|
|
annotationHandlerMethod.run(dataMethodAnnotationTriple.c, dataMethodAnnotationTriple.b, dataMethodAnnotationTriple.a);
|
|
});
|
|
});
|
|
}
|
|
rootNode.sort();
|
|
initialized = true;
|
|
}
|
|
|
|
// TODO: Implement this when Message System is ready
|
|
/*
|
|
public void addDefaultHelpMessage(String message) {
|
|
defaultHelpMessages.add(message);
|
|
}
|
|
*/
|
|
|
|
private List<Method> methods(Object commandObject) {
|
|
List<Method> methods = new ArrayList<>();
|
|
Class<?> current = commandObject.getClass();
|
|
while (current != null) {
|
|
methods.addAll(Arrays.asList(current.getDeclaredMethods()));
|
|
current = current.getSuperclass();
|
|
}
|
|
return methods;
|
|
}
|
|
}
|