/* * 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 java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; public class SubCommand implements Comparable> { private AbstractSWCommand abstractSWCommand; Method method; String[] description; String[] subCommand; private Predicate senderPredicate; private Function senderFunction; AbstractValidator validator; boolean noTabComplete; private Parameter[] parameters; private CommandPart commandPart; boolean isHelp = false; SubCommand(AbstractSWCommand abstractSWCommand, Method method, String[] subCommand, Map> localTypeMapper, Map> localValidator, String[] description, boolean noTabComplete) { this.abstractSWCommand = abstractSWCommand; this.method = method; try { this.method.setAccessible(true); } catch (SecurityException e) { throw new SecurityException(e.getMessage(), e); } this.subCommand = subCommand; this.description = description; this.noTabComplete = noTabComplete; parameters = method.getParameters(); AbstractSWCommand.Validator validator = parameters[0].getAnnotation(AbstractSWCommand.Validator.class); if (validator != null) { this.validator = (AbstractValidator) SWCommandUtils.getValidator(validator, parameters[0].getType(), localValidator); if (validator.invert()) { AbstractValidator current = this.validator; this.validator = (sender, value, messageSender) -> !current.validate(sender, value, messageSender); } } commandPart = generateCommandPart(abstractSWCommand, subCommand, parameters, localTypeMapper, localValidator); if (commandPart != null) isHelp = commandPart.isHelp(); senderPredicate = t -> parameters[0].getType().isAssignableFrom(t.getClass()); senderFunction = t -> parameters[0].getType().cast(t); } @Override public int compareTo(SubCommand o) { int tLength = parameters.length + subCommand.length; int oLength = o.parameters.length + o.subCommand.length; boolean tVarArgs = parameters[parameters.length - 1].isVarArgs(); boolean oVarArgs = o.parameters[o.parameters.length - 1].isVarArgs(); if (tVarArgs) tLength *= -1; if (oVarArgs) oLength *= -1; if (tVarArgs && oVarArgs) return Integer.compare(tLength, oLength); return -Integer.compare(tLength, oLength); } boolean invoke(Consumer errors, T sender, String alias, String[] args) { try { if (!senderPredicate.test(sender)) { return false; } if (commandPart == null) { if (args.length != 0) { return false; } if (validator != null) { if (!validator.validate(sender, sender, (s, objectArgs) -> { abstractSWCommand.sendMessage(sender, s, objectArgs); })) { return false; } } method.invoke(abstractSWCommand, senderFunction.apply(sender)); } else { List objects = new ArrayList<>(); commandPart.generateArgumentArray(errors, objects, sender, args, 0); if (validator != null) { if (!validator.validate(sender, sender, (s, objectArgs) -> { abstractSWCommand.sendMessage(sender, s, objectArgs); })) { return false; } } objects.add(0, senderFunction.apply(sender)); method.invoke(abstractSWCommand, objects.toArray()); } } catch (CommandFrameworkException e) { throw e; } catch (CommandParseException e) { return false; } catch (InvocationTargetException e) { throw new CommandFrameworkException(e, alias, args); } catch (IllegalAccessException | RuntimeException e) { throw new SecurityException(e.getMessage(), e); } return true; } List tabComplete(T sender, String[] args) { if (validator != null && !validator.validate(sender, sender, (s, objects) -> {})) { return null; } if (commandPart == null) { return null; } List list = new ArrayList<>(); commandPart.generateTabComplete(list, sender, args, new ArrayList<>(), 0); return list; } private static CommandPart generateCommandPart(AbstractSWCommand command, String[] subCommand, Parameter[] parameters, Map> localTypeMapper, Map> localValidator) { CommandPart first = null; CommandPart current = null; for (String s : subCommand) { CommandPart commandPart = new CommandPart(command, SWCommandUtils.createMapper(s), null, null, null, -1); commandPart.addValidator(NULL_FILTER); commandPart.setIgnoreAsArgument(true); if (current != null) { current.setNext(commandPart); } current = commandPart; if (first == null) { first = current; } } for (int i = 1; i < parameters.length; i++) { Parameter parameter = parameters[i]; AbstractTypeMapper typeMapper = handleImplicitTypeMapper(parameter, localTypeMapper); Class varArgType = parameter.isVarArgs() ? parameter.getType().getComponentType() : null; AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class); CommandPart commandPart = new CommandPart<>(command, typeMapper, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i); commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG()); handleImplicitTypeValidator(parameter, commandPart, localValidator); if (parameter.getAnnotation(AbstractSWCommand.AllowNull.class) == null) { commandPart.addValidator((AbstractValidator) NULL_FILTER); } if (current != null) { current.setNext(commandPart); } current = commandPart; if (first == null) { first = current; } } return first; } private static AbstractTypeMapper handleImplicitTypeMapper(Parameter parameter, Map> localTypeMapper) { Class type = parameter.getType(); if (parameter.isVarArgs()) { type = type.getComponentType(); } Annotation[] annotations = parameter.getAnnotations(); Constructor sourceConstructor = null; Annotation sourceAnnotation = null; List> parentConstructors = new ArrayList<>(); List parentAnnotations = new ArrayList<>(); for (Annotation annotation : annotations) { CommandMetaData.ImplicitTypeMapper implicitTypeMapper = annotation.annotationType().getAnnotation(CommandMetaData.ImplicitTypeMapper.class); if (implicitTypeMapper == null) continue; Class clazz = implicitTypeMapper.handler(); if (!AbstractTypeMapper.class.isAssignableFrom(clazz)) continue; Constructor[] constructors = clazz.getConstructors(); if (constructors.length != 1) continue; Constructor constructor = constructors[0]; if (needsTypeMapper(constructor)) { parentConstructors.add(constructor); parentAnnotations.add(annotation); } else { if (sourceAnnotation != null) { throw new IllegalArgumentException("Multiple source type mappers found for parameter " + parameter); } sourceConstructor = constructor; sourceAnnotation = annotation; } } AbstractTypeMapper current; if (sourceAnnotation != null) { current = createInstance(sourceConstructor, sourceAnnotation, type, localTypeMapper); } else { current = (AbstractTypeMapper) SWCommandUtils.getTypeMapper(parameter, localTypeMapper); } for (int i = 0; i < parentConstructors.size(); i++) { Constructor constructor = parentConstructors.get(i); Annotation annotation = parentAnnotations.get(i); current = createInstance(constructor, annotation, type, localTypeMapper, current); } return current; } private static boolean needsTypeMapper(Constructor constructor) { Class[] parameterTypes = constructor.getParameterTypes(); for (Class parameterType : parameterTypes) { if (AbstractTypeMapper.class.isAssignableFrom(parameterType)) { return true; } } return false; } private static void handleImplicitTypeValidator(Parameter parameter, CommandPart commandPart, Map> localValidator) { Annotation[] annotations = parameter.getAnnotations(); Map>> validators = new HashMap<>(); for (Annotation annotation : annotations) { CommandMetaData.ImplicitValidator implicitValidator = annotation.annotationType().getAnnotation(CommandMetaData.ImplicitValidator.class); if (implicitValidator == null) continue; Class clazz = implicitValidator.handler(); if (!AbstractValidator.class.isAssignableFrom(clazz)) continue; Constructor[] constructors = clazz.getConstructors(); if (constructors.length != 1) continue; AbstractValidator validator = createInstance(constructors[0], annotation, commandPart.getType(), localValidator); validators.computeIfAbsent(implicitValidator.order(), integer -> new ArrayList<>()).add(validator); } List keys = new ArrayList<>(validators.keySet()); keys.sort(Integer::compareTo); for (Integer key : keys) { List> list = validators.get(key); for (AbstractValidator validator : list) { commandPart.addValidator(validator); } } } private static T createInstance(Constructor constructor, Object... parameter) { Class[] types = constructor.getParameterTypes(); List objects = new ArrayList<>(); for (Class clazz : types) { boolean found = false; for (Object o : parameter) { if (clazz.isAssignableFrom(o.getClass())) { objects.add(o); found = true; break; } } if (!found) { throw new RuntimeException("Could not find type " + clazz + " for constructor " + constructor); } } try { return (T) constructor.newInstance(objects.toArray()); } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } } private static final AbstractValidator NULL_FILTER = (sender, value, messageSender) -> value != null; }