/* * 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 . */ 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 { private static final Map>, Map, Function>> EXECUTOR_MAPPER = new HashMap<>(); public static void addExecutorMapper(Class> wrapper, Class executorType, Function mapper) { EXECUTOR_MAPPER.computeIfAbsent(wrapper, __ -> new HashMap<>()) .putIfAbsent(executorType, mapper); } protected final String command; protected final String permission; private final Consumer> initializer; protected final String[] aliases; protected final List descriptions = new ArrayList<>(); private boolean initialized = false; protected final RootNode rootNode = new RootNode<>(); private final Map> localTypeMapper = new HashMap<>(); private final Map> localValidators = new HashMap<>(); private final Map> localSupplier = new HashMap<>(); protected AbstractCommand(String command, Consumer> initializer, String... aliases) { this(command, null, initializer, aliases); } protected AbstractCommand(String command, String permission, Consumer> 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 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 tabComplete(T sender, String alias, String[] args) throws IllegalArgumentException { initialize(); List> 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 commandObjects = new ArrayList<>(); initializer.accept(new CommandLoader<>() { @Override public AbstractCommand get() { return AbstractCommand.this; } @Override public void add(Object command) { commandObjects.add(command); } }); List> 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, List>>> handlerMap = new HashMap<>(); for (Pair commandMethod : methods) { List 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) handlerObject); } } List 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 methods(Object commandObject) { List methods = new ArrayList<>(); Class current = commandObject.getClass(); while (current != null) { methods.addAll(Arrays.asList(current.getDeclaredMethods())); current = current.getSuperclass(); } return methods; } }