Add BetterException handling for exceptions in tabComplete and validate since there now exception should be thrown there
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful
Alle Prüfungen waren erfolgreich
SteamWarCI Build successful
Dieser Commit ist enthalten in:
Ursprung
9df92595b2
Commit
9439cc81a5
@ -21,39 +21,79 @@ package de.steamwar.command;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class CommandFrameworkException extends RuntimeException {
|
||||
|
||||
private InvocationTargetException invocationTargetException;
|
||||
private String alias;
|
||||
private String[] args;
|
||||
private Function causeMessage;
|
||||
private Throwable cause;
|
||||
private Function stackTraceExtractor;
|
||||
private String extraStackTraces;
|
||||
|
||||
private String message;
|
||||
|
||||
static CommandFrameworkException commandGetExceptions(String type, Class<?> clazzType, Executable executable, int index) {
|
||||
return new CommandFrameworkException(throwable -> {
|
||||
return CommandFrameworkException.class.getTypeName() + ": Error while getting " + type + " for " + clazzType.getTypeName() + " with parameter index " + index;
|
||||
}, null, throwable -> Stream.empty(), executable.getDeclaringClass().getTypeName() + "." + executable.getName() + "(Unknown Source)");
|
||||
}
|
||||
|
||||
static CommandFrameworkException commandPartExceptions(String type, Throwable cause, String current, Class<?> clazzType, Executable executable, int index) {
|
||||
return new CommandFrameworkException(e -> {
|
||||
return CommandFrameworkException.class.getTypeName() + ": Error while " + type + " (" + current + ") to type " + clazzType.getTypeName() + " with parameter index " + index;
|
||||
}, cause, exception -> {
|
||||
StackTraceElement[] stackTraceElements = exception.getStackTrace();
|
||||
int last = 0;
|
||||
for (int i = 0; i < stackTraceElements.length; i++) {
|
||||
if (stackTraceElements[i].getClassName().equals(CommandPart.class.getTypeName())) {
|
||||
last = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Arrays.stream(stackTraceElements).limit(last - 1);
|
||||
}, executable.getDeclaringClass().getTypeName() + "." + executable.getName() + "(Unknown Source)");
|
||||
}
|
||||
|
||||
CommandFrameworkException(InvocationTargetException invocationTargetException, String alias, String[] args) {
|
||||
super(invocationTargetException);
|
||||
this.invocationTargetException = invocationTargetException;
|
||||
this.alias = alias;
|
||||
this.args = args;
|
||||
this(e -> {
|
||||
StringBuilder st = new StringBuilder();
|
||||
st.append(e.getCause().getClass().getTypeName());
|
||||
if (e.getCause().getMessage() != null) {
|
||||
st.append(": ").append(e.getCause().getMessage());
|
||||
}
|
||||
if (alias != null && !alias.isEmpty()) {
|
||||
st.append("\n").append("Performed command: " + alias + " " + String.join(" ", args));
|
||||
}
|
||||
return st.toString();
|
||||
}, invocationTargetException, e -> {
|
||||
StackTraceElement[] stackTraceElements = e.getCause().getStackTrace();
|
||||
return Arrays.stream(stackTraceElements).limit(stackTraceElements.length - e.getStackTrace().length);
|
||||
}, null);
|
||||
}
|
||||
|
||||
private <T extends Throwable> CommandFrameworkException(Function<T, String> causeMessage, T cause, Function<T, Stream<StackTraceElement>> stackTraceExtractor, String extraStackTraces) {
|
||||
super(causeMessage.apply(cause), cause);
|
||||
this.causeMessage = causeMessage;
|
||||
this.cause = cause;
|
||||
this.stackTraceExtractor = stackTraceExtractor;
|
||||
this.extraStackTraces = extraStackTraces;
|
||||
}
|
||||
|
||||
public synchronized String getBuildStackTrace() {
|
||||
if (message != null) {
|
||||
return message;
|
||||
}
|
||||
StackTraceElement[] stackTraceElements = invocationTargetException.getCause().getStackTrace();
|
||||
StringBuilder st = new StringBuilder();
|
||||
st.append(invocationTargetException.getCause().getClass().getTypeName());
|
||||
if (invocationTargetException.getCause().getMessage() != null) {
|
||||
st.append(": ").append(invocationTargetException.getCause().getMessage());
|
||||
}
|
||||
st.append("\n");
|
||||
if (alias != null && !alias.isEmpty()) {
|
||||
st.append("Performed command: ").append(alias).append(" ").append(String.join(" ", args)).append("\n");
|
||||
}
|
||||
for (int i = 0; i < stackTraceElements.length - invocationTargetException.getStackTrace().length; i++) {
|
||||
st.append("\tat ").append(stackTraceElements[i].toString()).append("\n");
|
||||
st.append(causeMessage.apply(cause)).append("\n");
|
||||
((Stream<StackTraceElement>) stackTraceExtractor.apply(cause)).forEach(stackTraceElement -> {
|
||||
st.append("\tat ").append(stackTraceElement.toString()).append("\n");
|
||||
});
|
||||
if (extraStackTraces != null) {
|
||||
st.append("\tat ").append(extraStackTraces).append("\n");
|
||||
}
|
||||
message = st.toString();
|
||||
return message;
|
||||
|
@ -23,7 +23,7 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@ -56,34 +56,31 @@ class CommandPart<T> {
|
||||
@Setter
|
||||
private boolean allowNullValues = false;
|
||||
|
||||
public CommandPart(AbstractSWCommand<T> command, AbstractTypeMapper<T, ?> typeMapper, AbstractValidator<T, Object> validator, Class<?> varArgType, String optional) {
|
||||
private Parameter parameter;
|
||||
private int parameterIndex;
|
||||
|
||||
public CommandPart(AbstractSWCommand<T> command, AbstractTypeMapper<T, ?> typeMapper, AbstractValidator<T, Object> validator, Class<?> varArgType, String optional, Parameter parameter, int parameterIndex) {
|
||||
this.command = command;
|
||||
this.typeMapper = typeMapper;
|
||||
this.validator = validator;
|
||||
this.varArgType = varArgType;
|
||||
this.optional = optional;
|
||||
this.parameter = parameter;
|
||||
this.parameterIndex = parameterIndex;
|
||||
|
||||
validatePart();
|
||||
}
|
||||
|
||||
public void setNext(CommandPart<T> next) {
|
||||
if (varArgType != null) {
|
||||
throw new IllegalArgumentException("There can't be a next part if this is a vararg part!");
|
||||
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;
|
||||
}
|
||||
|
||||
private void validatePart() {
|
||||
if (optional != null && varArgType != null) {
|
||||
throw new IllegalArgumentException("A vararg part can't have an optional part!");
|
||||
}
|
||||
|
||||
if (optional != null) {
|
||||
try {
|
||||
typeMapper.map(null, EMPTY_ARRAY, optional);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("The optional part is not valid!");
|
||||
}
|
||||
throw new IllegalArgumentException("A vararg part can't have an optional part! In method " + parameter.getDeclaringExecutable() + " with parameter " + parameterIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +111,7 @@ class CommandPart<T> {
|
||||
if (!ignoreAsArgument) {
|
||||
if (!onlyUseIfNoneIsGiven) {
|
||||
current.add(typeMapper.map(sender, EMPTY_ARRAY, optional));
|
||||
} else if(startIndex >= args.length) {
|
||||
} else if (startIndex >= args.length) {
|
||||
current.add(typeMapper.map(sender, EMPTY_ARRAY, optional));
|
||||
} else {
|
||||
throw new CommandParseException();
|
||||
@ -171,14 +168,23 @@ class CommandPart<T> {
|
||||
|
||||
private Collection<String> tabCompletes(T sender, String[] args, int startIndex) {
|
||||
return TabCompletionCache.tabComplete(sender, typeMapper, command, () -> {
|
||||
return typeMapper.tabCompletes(sender, Arrays.copyOf(args, startIndex), args[startIndex]);
|
||||
try {
|
||||
return typeMapper.tabCompletes(sender, Arrays.copyOf(args, startIndex), args[startIndex]);
|
||||
} catch (Throwable e) {
|
||||
throw CommandFrameworkException.commandPartExceptions("tabcompleting", e, args[startIndex], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private CheckArgumentResult checkArgument(Consumer<Runnable> errors, T sender, String[] args, int index) {
|
||||
Object value;
|
||||
try {
|
||||
Object value = typeMapper.map(sender, Arrays.copyOf(args, index), args[index]);
|
||||
if (validator != null && errors != null) {
|
||||
value = typeMapper.map(sender, Arrays.copyOf(args, index), args[index]);
|
||||
} catch (Exception e) {
|
||||
return new CheckArgumentResult(false, null);
|
||||
}
|
||||
if (validator != null && errors != null) {
|
||||
try {
|
||||
if (!validator.validate(sender, value, (s, objects) -> {
|
||||
errors.accept(() -> {
|
||||
command.sendMessage(sender, s, objects);
|
||||
@ -186,10 +192,10 @@ class CommandPart<T> {
|
||||
})) {
|
||||
return new CheckArgumentResult(false, null);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw CommandFrameworkException.commandPartExceptions("validating", e, args[index], (varArgType != null ? varArgType : parameter.getType()), parameter.getDeclaringExecutable(), parameterIndex);
|
||||
}
|
||||
return new CheckArgumentResult(allowNullValues || value != null, value);
|
||||
} catch (Exception e) {
|
||||
return new CheckArgumentResult(false, null);
|
||||
}
|
||||
return new CheckArgumentResult(allowNullValues || value != null, value);
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ public class SWCommandUtils {
|
||||
CommandPart<T> first = null;
|
||||
CommandPart<T> current = null;
|
||||
for (String s : subCommand) {
|
||||
CommandPart commandPart = new CommandPart(command, createMapper(s), null, null, null);
|
||||
CommandPart commandPart = new CommandPart(command, createMapper(s), null, null, null, null, -1);
|
||||
commandPart.setIgnoreAsArgument(true);
|
||||
if (current != null) {
|
||||
current.setNext(commandPart);
|
||||
@ -96,7 +96,7 @@ public class SWCommandUtils {
|
||||
AbstractSWCommand.OptionalValue optionalValue = parameter.getAnnotation(AbstractSWCommand.OptionalValue.class);
|
||||
AbstractSWCommand.AllowNull allowNull = parameter.getAnnotation(AbstractSWCommand.AllowNull.class);
|
||||
|
||||
CommandPart<T> commandPart = new CommandPart<>(command, typeMapper, validator, varArgType, optionalValue != null ? optionalValue.value() : null);
|
||||
CommandPart<T> commandPart = new CommandPart<>(command, typeMapper, validator, varArgType, optionalValue != null ? optionalValue.value() : null, parameter, i);
|
||||
commandPart.setOnlyUseIfNoneIsGiven(optionalValue != null && optionalValue.onlyUINIG());
|
||||
commandPart.setAllowNullValues(allowNull != null);
|
||||
if (current != null) {
|
||||
|
@ -97,7 +97,7 @@ public class SubCommand<T> {
|
||||
objects.add(0, senderFunction.apply(sender));
|
||||
method.invoke(abstractSWCommand, objects.toArray());
|
||||
}
|
||||
} catch (CommandNoHelpException e) {
|
||||
} catch (CommandNoHelpException | CommandFrameworkException e) {
|
||||
throw e;
|
||||
} catch (CommandParseException e) {
|
||||
return false;
|
||||
|
57
testsrc/de/steamwar/command/BetterExceptionCommand.java
Normale Datei
57
testsrc/de/steamwar/command/BetterExceptionCommand.java
Normale Datei
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2022 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.dto.TestSWCommand;
|
||||
import de.steamwar.command.dto.TestTypeMapper;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class BetterExceptionCommand extends TestSWCommand {
|
||||
|
||||
public BetterExceptionCommand() {
|
||||
super("betterexception");
|
||||
}
|
||||
|
||||
@Register
|
||||
public void exceptionOnTabComplete(String s, @Mapper("exception") @Validator("exception") String s1) {
|
||||
}
|
||||
|
||||
@Mapper("exception")
|
||||
@Validator("exception")
|
||||
public TestTypeMapper<String> tabCompleteException() {
|
||||
return new TestTypeMapper<String>() {
|
||||
@Override
|
||||
public String map(String sender, String[] previousArguments, String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validate(String sender, String value, MessageSender messageSender) {
|
||||
throw new SecurityException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> tabCompletes(String sender, String[] previousArguments, String s) {
|
||||
throw new SecurityException();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
53
testsrc/de/steamwar/command/BetterExceptionCommandTest.java
Normale Datei
53
testsrc/de/steamwar/command/BetterExceptionCommandTest.java
Normale Datei
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2022 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 org.junit.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class BetterExceptionCommandTest {
|
||||
|
||||
@Test
|
||||
public void testTabCompleteException() {
|
||||
BetterExceptionCommand cmd = new BetterExceptionCommand();
|
||||
try {
|
||||
cmd.tabComplete("test", "", new String[]{""});
|
||||
assert false;
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(CommandFrameworkException.class)));
|
||||
assertThat(e.getMessage(), is("de.steamwar.command.CommandFrameworkException: Error while tabcompleting () to type java.lang.String with parameter index 1"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidationException() {
|
||||
BetterExceptionCommand cmd = new BetterExceptionCommand();
|
||||
try {
|
||||
cmd.execute("test", "", new String[]{""});
|
||||
assert false;
|
||||
} catch (Exception e) {
|
||||
assertThat(e, is(instanceOf(CommandFrameworkException.class)));
|
||||
assertThat(e.getMessage(), is("de.steamwar.command.CommandFrameworkException: Error while validating () to type java.lang.String with parameter index 1"));
|
||||
}
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren