Made all exceptions throw with context

This commit is contained in:
Nathan Adams
2017-07-27 09:58:14 +02:00
parent 9276feddd4
commit 5181559f46
16 changed files with 275 additions and 61 deletions
@@ -22,7 +22,7 @@ import java.util.stream.Collectors;
public class CommandDispatcher<S> {
public static final SimpleCommandExceptionType ERROR_UNKNOWN_COMMAND = new SimpleCommandExceptionType("command.unknown.command", "Unknown command");
public static final ParameterizedCommandExceptionType ERROR_UNKNOWN_ARGUMENT = new ParameterizedCommandExceptionType("command.unknown.argument", "Incorrect argument for command, couldn't parse: ${argument}", "argument");
public static final SimpleCommandExceptionType ERROR_UNKNOWN_ARGUMENT = new SimpleCommandExceptionType("command.unknown.argument", "Incorrect argument for command");
public static final SimpleCommandExceptionType ERROR_EXPECTED_ARGUMENT_SEPARATOR = new SimpleCommandExceptionType("command.expected.separator", "Expected whitespace to end one argument, but found trailing data");
public static final String ARGUMENT_SEPARATOR = " ";
@@ -52,19 +52,19 @@ public class CommandDispatcher<S> {
}
public int execute(final ParseResults<S> parse) throws CommandException {
if (parse.getRemaining().length() > 0) {
if (parse.getReader().canRead()) {
if (parse.getExceptions().size() == 1) {
throw parse.getExceptions().values().iterator().next();
} else if (parse.getContext().getInput().isEmpty()) {
throw ERROR_UNKNOWN_COMMAND.create();
throw ERROR_UNKNOWN_COMMAND.createWithContext(parse.getReader());
} else {
throw ERROR_UNKNOWN_ARGUMENT.create(parse.getRemaining());
throw ERROR_UNKNOWN_ARGUMENT.createWithContext(parse.getReader());
}
}
final CommandContext<S> context = parse.getContext().build();
final Command<S> command = context.getCommand();
if (command == null) {
throw ERROR_UNKNOWN_COMMAND.create();
throw ERROR_UNKNOWN_COMMAND.createWithContext(parse.getReader());
}
return command.run(context);
}
@@ -95,7 +95,7 @@ public class CommandDispatcher<S> {
context.withCommand(child.getCommand());
if (reader.canRead()) {
if (reader.peek() != ARGUMENT_SEPARATOR_CHAR) {
throw ERROR_EXPECTED_ARGUMENT_SEPARATOR.create();
throw ERROR_EXPECTED_ARGUMENT_SEPARATOR.createWithContext(reader);
}
reader.skip();
return parseNodes(child, reader, context);
@@ -104,7 +104,7 @@ public class CommandDispatcher<S> {
}
}
return new ParseResults<>(contextBuilder, reader.getRemaining(), errors);
return new ParseResults<>(contextBuilder, reader, errors);
}
public String[] getAllUsage(final CommandNode<S> node, final S source) {
@@ -0,0 +1,23 @@
package com.mojang.brigadier;
public interface ImmutableStringReader {
String getString();
int getRemainingLength();
int getTotalLength();
int getCursor();
String getRead();
String getRemaining();
boolean canRead(int length);
boolean canRead();
char peek();
char peek(int offset);
}
@@ -9,25 +9,25 @@ import java.util.Map;
public class ParseResults<S> {
private final CommandContextBuilder<S> context;
private final String remaining;
private final Map<CommandNode<S>, CommandException> exceptions;
private final ImmutableStringReader reader;
public ParseResults(final CommandContextBuilder<S> context, final String remaining, final Map<CommandNode<S>, CommandException> exceptions) {
public ParseResults(final CommandContextBuilder<S> context, final ImmutableStringReader reader, final Map<CommandNode<S>, CommandException> exceptions) {
this.context = context;
this.remaining = remaining;
this.reader = reader;
this.exceptions = exceptions;
}
public ParseResults(final CommandContextBuilder<S> context) {
this(context, "", Collections.emptyMap());
this(context, new StringReader(""), Collections.emptyMap());
}
public CommandContextBuilder<S> getContext() {
return context;
}
public String getRemaining() {
return remaining;
public ImmutableStringReader getReader() {
return reader;
}
public Map<CommandNode<S>, CommandException> getExceptions() {
@@ -4,7 +4,7 @@ import com.mojang.brigadier.exceptions.CommandException;
import com.mojang.brigadier.exceptions.ParameterizedCommandExceptionType;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
public class StringReader {
public class StringReader implements ImmutableStringReader {
private static final char SYNTAX_ESCAPE = '\\';
private static final char SYNTAX_QUOTE = '"';
@@ -16,6 +16,7 @@ public class StringReader {
public static final SimpleCommandExceptionType ERROR_EXPECTED_INT = new SimpleCommandExceptionType("parsing.int.expected", "Expected integer");
public static final ParameterizedCommandExceptionType ERROR_INVALID_DOUBLE = new ParameterizedCommandExceptionType("parsing.double.invalid", "Invalid double '${value}'", "value");
public static final SimpleCommandExceptionType ERROR_EXPECTED_DOUBLE = new SimpleCommandExceptionType("parsing.double.expected", "Expected double");
public static final SimpleCommandExceptionType ERROR_EXPECTED_BOOL = new SimpleCommandExceptionType("parsing.bool.expected", "Expected bool");
public static final ParameterizedCommandExceptionType ERROR_EXPECTED_SYMBOL = new ParameterizedCommandExceptionType("parsing.expected", "Expected '${symbol}'", "symbol");
private final String string;
@@ -25,6 +26,7 @@ public class StringReader {
this.string = string;
}
@Override
public String getString() {
return string;
}
@@ -33,38 +35,47 @@ public class StringReader {
this.cursor = cursor;
}
@Override
public int getRemainingLength() {
return string.length() - cursor;
}
@Override
public int getTotalLength() {
return string.length();
}
@Override
public int getCursor() {
return cursor;
}
@Override
public String getRead() {
return string.substring(0, cursor);
}
@Override
public String getRemaining() {
return string.substring(cursor);
}
@Override
public boolean canRead(final int length) {
return cursor + length <= string.length();
}
@Override
public boolean canRead() {
return canRead(1);
}
@Override
public char peek() {
return string.charAt(cursor);
}
@Override
public char peek(final int offset) {
return string.charAt(cursor + offset);
}
@@ -94,12 +105,13 @@ public class StringReader {
}
final String number = string.substring(start, cursor);
if (number.isEmpty()) {
throw ERROR_EXPECTED_INT.create();
throw ERROR_EXPECTED_INT.createWithContext(this);
}
try {
return Integer.parseInt(number);
} catch (final NumberFormatException ex) {
throw ERROR_INVALID_INT.create(number);
cursor = start;
throw ERROR_INVALID_INT.createWithContext(this, number);
}
}
@@ -110,12 +122,13 @@ public class StringReader {
}
final String number = string.substring(start, cursor);
if (number.isEmpty()) {
throw ERROR_EXPECTED_DOUBLE.create();
throw ERROR_EXPECTED_DOUBLE.createWithContext(this);
}
try {
return Double.parseDouble(number);
} catch (final NumberFormatException ex) {
throw ERROR_INVALID_DOUBLE.create(number);
cursor = start;
throw ERROR_INVALID_DOUBLE.createWithContext(this, number);
}
}
@@ -139,7 +152,7 @@ public class StringReader {
if (!canRead()) {
return "";
} else if (peek() != SYNTAX_QUOTE) {
throw ERROR_EXPECTED_START_OF_QUOTE.create();
throw ERROR_EXPECTED_START_OF_QUOTE.createWithContext(this);
}
skip();
final StringBuilder result = new StringBuilder();
@@ -151,7 +164,8 @@ public class StringReader {
result.append(c);
escaped = false;
} else {
throw ERROR_INVALID_ESCAPE.create(String.valueOf(c));
setCursor(getCursor() - 1);
throw ERROR_INVALID_ESCAPE.createWithContext(this, String.valueOf(c));
}
} else if (c == SYNTAX_ESCAPE) {
escaped = true;
@@ -162,7 +176,7 @@ public class StringReader {
}
}
throw ERROR_EXPECTED_END_OF_QUOTE.create();
throw ERROR_EXPECTED_END_OF_QUOTE.createWithContext(this);
}
public String readString() throws CommandException {
@@ -174,19 +188,25 @@ public class StringReader {
}
public boolean readBoolean() throws CommandException {
final int start = cursor;
final String value = readString();
if (value.isEmpty()) {
throw ERROR_EXPECTED_BOOL.createWithContext(this);
}
if (value.equals("true")) {
return true;
} else if (value.equals("false")) {
return false;
} else {
throw ERROR_INVALID_BOOL.create(value);
cursor = start;
throw ERROR_INVALID_BOOL.createWithContext(this, value);
}
}
public void expect(final char c) throws CommandException {
if (!canRead() || peek() != c) {
throw ERROR_EXPECTED_SYMBOL.create(String.valueOf(c));
throw ERROR_EXPECTED_SYMBOL.createWithContext(this, String.valueOf(c));
}
skip();
}
@@ -45,19 +45,23 @@ public class IntegerArgumentType implements ArgumentType<Integer> {
@Override
public <S> Integer parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandException {
final int start = reader.getCursor();
final int result = reader.readInt();
for (int i = 0; i < suffix.length(); i++) {
if (reader.canRead() && reader.peek() == suffix.charAt(i)) {
reader.skip();
} else {
throw ERROR_WRONG_SUFFIX.create(suffix);
reader.setCursor(start);
throw ERROR_WRONG_SUFFIX.createWithContext(reader, suffix);
}
}
if (result < minimum) {
throw ERROR_TOO_SMALL.create(result, minimum);
reader.setCursor(start);
throw ERROR_TOO_SMALL.createWithContext(reader, result, minimum);
}
if (result > maximum) {
throw ERROR_TOO_BIG.create(result, maximum);
reader.setCursor(start);
throw ERROR_TOO_BIG.createWithContext(reader, result, maximum);
}
return result;
}
@@ -3,27 +3,70 @@ package com.mojang.brigadier.exceptions;
import java.util.Map;
public class CommandException extends Exception {
public static final int CONTEXT_AMOUNT = 10;
public static boolean ENABLE_COMMAND_STACK_TRACES = true;
private final CommandExceptionType type;
private final Map<String, Object> data;
private final Map<String, String> data;
private final String input;
private final int cursor;
public CommandException(final CommandExceptionType type, final Map<String, Object> data) {
public CommandException(final CommandExceptionType type, final Map<String, String> data) {
super(type.getTypeName(), null, ENABLE_COMMAND_STACK_TRACES, ENABLE_COMMAND_STACK_TRACES);
this.type = type;
this.data = data;
this.input = null;
this.cursor = -1;
}
public CommandException(final CommandExceptionType type, final Map<String, String> data, final String input, final int cursor) {
super(type.getTypeName(), null, ENABLE_COMMAND_STACK_TRACES, ENABLE_COMMAND_STACK_TRACES);
this.type = type;
this.data = data;
this.input = input;
this.cursor = cursor;
}
@Override
public String getMessage() {
return type.getErrorMessage(this);
String message = type.getErrorMessage(data);
final String context = getContext();
if (context != null) {
message += " at position " + cursor + ": " + context;
}
return message;
}
public String getContext() {
if (input == null || cursor < 0) {
return null;
}
final StringBuilder builder = new StringBuilder();
final int cursor = Math.min(input.length(), this.cursor);
if (cursor > CONTEXT_AMOUNT) {
builder.append("...");
}
builder.append(input.substring(Math.max(0, cursor - CONTEXT_AMOUNT), cursor));
builder.append("<--[HERE]");
return builder.toString();
}
public CommandExceptionType getType() {
return type;
}
public Map<String, Object> getData() {
public Map<String, String> getData() {
return data;
}
public String getInput() {
return input;
}
public int getCursor() {
return cursor;
}
}
@@ -1,7 +1,9 @@
package com.mojang.brigadier.exceptions;
import java.util.Map;
public interface CommandExceptionType {
String getTypeName();
String getErrorMessage(CommandException exception);
String getErrorMessage(Map<String, String> data);
}
@@ -2,7 +2,10 @@ package com.mojang.brigadier.exceptions;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.mojang.brigadier.ImmutableStringReader;
import com.mojang.brigadier.StringReader;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -26,28 +29,31 @@ public class ParameterizedCommandExceptionType implements CommandExceptionType {
}
@Override
public String getErrorMessage(final CommandException exception) {
public String getErrorMessage(final Map<String, String> data) {
final Matcher matcher = PATTERN.matcher(message);
final StringBuffer result = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(result, Matcher.quoteReplacement(exception.getData().get(matcher.group(1)).toString()));
matcher.appendReplacement(result, Matcher.quoteReplacement(data.get(matcher.group(1))));
}
matcher.appendTail(result);
return result.toString();
}
public CommandException create(final Object... values) {
public CommandException createWithContext(final ImmutableStringReader reader, final Object... values) {
return new CommandException(this, createMap(values), reader.getString(), reader.getCursor());
}
public Map<String, String> createMap(final Object... values) {
if (values.length != keys.length) {
throw new IllegalArgumentException("Invalid values! (Expected: " + JOINER.join(keys) + ")");
}
ImmutableMap.Builder<String, Object> builder = ImmutableMap.builder();
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
for (int i = 0; i < keys.length; i++) {
builder = builder.put(keys[i], values[i]);
builder = builder.put(keys[i], String.valueOf(values[i]));
}
return new CommandException(this, builder.build());
return builder.build();
}
@Override
@@ -1,6 +1,10 @@
package com.mojang.brigadier.exceptions;
import com.google.common.collect.ImmutableMap;
import com.mojang.brigadier.ImmutableStringReader;
import com.mojang.brigadier.StringReader;
import java.util.Map;
public class SimpleCommandExceptionType implements CommandExceptionType {
private final String name;
@@ -17,12 +21,12 @@ public class SimpleCommandExceptionType implements CommandExceptionType {
}
@Override
public String getErrorMessage(final CommandException exception) {
public String getErrorMessage(final Map<String, String> data) {
return message;
}
public CommandException create() {
return new CommandException(this, ImmutableMap.of());
public CommandException createWithContext(final ImmutableStringReader reader) {
return new CommandException(this, ImmutableMap.of(), reader.getString(), reader.getCursor());
}
@Override
@@ -31,11 +31,13 @@ public class LiteralCommandNode<S> extends CommandNode<S> {
@Override
public void parse(final StringReader reader, final CommandContextBuilder<S> contextBuilder) throws CommandException {
final int start = reader.getCursor();
for (int i = 0; i < literal.length(); i++) {
if (reader.canRead() && reader.peek() == literal.charAt(i)) {
reader.skip();
} else {
throw ERROR_INCORRECT_LITERAL.create(literal);
reader.setCursor(start);
throw ERROR_INCORRECT_LITERAL.createWithContext(reader, literal);
}
}