Files
SteamWar/CommandFramework/CommandFrameworkBase/src/de/steamwar/command/AbstractCommand.java
T
2025-11-24 10:00:45 +01:00

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;
}
}