/* * 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 lombok.AllArgsConstructor; import lombok.Setter; import java.lang.reflect.Array; import java.lang.reflect.Parameter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.function.Consumer; class CommandPart { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; @AllArgsConstructor private static class CheckArgumentResult { private final boolean success; private final Object value; } private AbstractSWCommand command; private AbstractTypeMapper typeMapper; private List> validators = new ArrayList<>(); private Class varArgType; private String optional; private CommandPart next = null; @Setter private boolean ignoreAsArgument = false; @Setter private boolean onlyUseIfNoneIsGiven = false; private Parameter parameter; private int parameterIndex; public CommandPart(AbstractSWCommand command, AbstractTypeMapper typeMapper, Class varArgType, String optional, Parameter parameter, int parameterIndex) { this.command = command; this.typeMapper = typeMapper; this.varArgType = varArgType; this.optional = optional; this.parameter = parameter; this.parameterIndex = parameterIndex; if (optional != null && varArgType != null) { throw new IllegalArgumentException("A vararg part can't have an optional part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); } } void addValidator(AbstractValidator validator) { if (validator == null) return; validators.add(validator); } public void setNext(CommandPart next) { if (varArgType != null) { throw new IllegalArgumentException("There can't be a next part if this is a vararg part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex); } this.next = next; } public boolean isHelp() { if (next == null) { if (varArgType == null) { return false; } if (varArgType != String.class) { return false; } return typeMapper == SWCommandUtils.STRING_MAPPER; } else { return next.isHelp(); } } public void generateArgumentArray(Consumer errors, List current, T sender, String[] args, int startIndex) { if (varArgType != null) { Object array = Array.newInstance(varArgType, args.length - startIndex); for (int i = startIndex; i < args.length; i++) { CheckArgumentResult validArgument = checkArgument(null, sender, args, current, i); if (!validArgument.success) throw new CommandParseException(); Array.set(array, i - startIndex, validArgument.value); } for (AbstractValidator validator : validators) { if (!validator.validate(sender, array, (s, objects) -> { errors.accept(() -> command.sendMessage(sender, s, objects)); })) throw new CommandParseException(); } current.add(array); return; } CheckArgumentResult validArgument = checkArgument(errors, sender, args, current, startIndex); if (!validArgument.success && optional == null) { throw new CommandParseException(); } if (!validArgument.success) { if (!ignoreAsArgument) { if (!onlyUseIfNoneIsGiven) { current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); } else if (startIndex >= args.length) { current.add(typeMapper.map(sender, new PreviousArguments(EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY), optional)); } else { throw new CommandParseException(); } } if (next != null) { next.generateArgumentArray(errors, current, sender, args, startIndex); } return; } if (!ignoreAsArgument) { current.add(validArgument.value); } if (next != null) { next.generateArgumentArray(errors, current, sender, args, startIndex + 1); } else if (startIndex + 1 < args.length) { throw new CommandParseException(); } } public void generateTabComplete(List current, T sender, String[] args, List mappedArgs, int startIndex) { if (varArgType != null) { List currentArgs = new ArrayList<>(mappedArgs); List varArgs = new ArrayList<>(); for (int i = startIndex; i < args.length - 1; i++) { CheckArgumentResult validArgument = checkArgument((ignore) -> {}, sender, args, mappedArgs, i); if (!validArgument.success) return; varArgs.add(validArgument.value); } currentArgs.add(varArgs.toArray()); Collection strings = tabCompletes(sender, args, currentArgs, args.length - 1); if (strings != null) { current.addAll(strings); } return; } if (args.length - 1 > startIndex) { CheckArgumentResult checkArgumentResult = checkArgument((ignore) -> {}, sender, args, mappedArgs, startIndex); if (checkArgumentResult.success && next != null) { if (!ignoreAsArgument) { mappedArgs.add(checkArgumentResult.value); } next.generateTabComplete(current, sender, args, mappedArgs, startIndex + 1); return; } if (optional != null && next != null) { next.generateTabComplete(current, sender, args, mappedArgs, startIndex); } return; } Collection strings = tabCompletes(sender, args, mappedArgs, startIndex); if (strings != null) { current.addAll(strings); } if (optional != null && next != null) { next.generateTabComplete(current, sender, args, mappedArgs, startIndex); } } private Collection tabCompletes(T sender, String[] args, List mappedArgs, int startIndex) { return TabCompletionCache.tabComplete(sender, args[startIndex], (AbstractTypeMapper) typeMapper, () -> { try { return typeMapper.tabCompletes(sender, new PreviousArguments(Arrays.copyOf(args, startIndex), mappedArgs.toArray()), args[startIndex]); } catch (Throwable e) { throw CommandFrameworkException.commandPartExceptions("tabcompleting", e, args[startIndex], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); } }); } private CheckArgumentResult checkArgument(Consumer errors, T sender, String[] args, List mappedArgs, int index) { Object value; try { value = typeMapper.map(sender, new PreviousArguments(Arrays.copyOf(args, index), mappedArgs.toArray()), args[index]); } catch (Exception e) { return new CheckArgumentResult(false, null); } boolean success = true; for (AbstractValidator validator : validators) { try { if (!validator.validate(sender, value, (s, objects) -> { errors.accept(() -> { command.sendMessage(sender, s, objects); }); })) { success = false; value = null; } } catch (Throwable e) { throw CommandFrameworkException.commandPartExceptions("validating", e, args[index], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex); } } return new CheckArgumentResult(success, value); } public Class getType() { return varArgType != null ? varArgType : parameter.getType(); } }