From 959d473d340af725f70221488eb08dae1fe14a20 Mon Sep 17 00:00:00 2001 From: Ashcon Partovi Date: Sat, 1 May 2021 10:24:43 -0700 Subject: [PATCH] Better line numbers for TextException Signed-off-by: Ashcon Partovi --- core/src/main/java/tc/oc/pgm/PGMPlugin.java | 44 ++++--- util/src/main/i18n/templates/misc.properties | 4 + .../tc/oc/pgm/util/text/TextException.java | 4 + .../java/tc/oc/pgm/util/text/TextParser.java | 48 ++++++- .../oc/pgm/util/xml/InvalidXMLException.java | 4 + .../java/tc/oc/pgm/util/xml/XMLUtils.java | 122 +++--------------- .../tc/oc/pgm/util/text/TextParserTest.java | 35 +++++ 7 files changed, 136 insertions(+), 125 deletions(-) diff --git a/core/src/main/java/tc/oc/pgm/PGMPlugin.java b/core/src/main/java/tc/oc/pgm/PGMPlugin.java index 4389d2a3c8..6c2300bf45 100644 --- a/core/src/main/java/tc/oc/pgm/PGMPlugin.java +++ b/core/src/main/java/tc/oc/pgm/PGMPlugin.java @@ -383,31 +383,39 @@ private String format(LogRecord record) { return record.getMessage(); } - final TextException textErr = tryException(TextException.class, thrown); - if (textErr != null) { - return format(null, textErr.getLocalizedMessage(), textErr.getCause()); - } - final InvalidXMLException xmlErr = tryException(InvalidXMLException.class, thrown); + final MapException mapErr = tryException(MapException.class, thrown); + final ModuleLoadException moduleErr = tryException(ModuleLoadException.class, thrown); + + String location = null; if (xmlErr != null) { - return format(xmlErr.getFullLocation(), xmlErr.getMessage(), xmlErr.getCause()); + location = xmlErr.getFullLocation(); + } else if (mapErr != null) { + location = mapErr.getLocation(); + } else if (moduleErr != null) { + final Class module = moduleErr.getModule(); + location = (module == null ? ModuleLoadException.class : module).getSimpleName(); } - final MapException mapErr = tryException(MapException.class, thrown); - if (mapErr != null) { - return format(mapErr.getLocation(), mapErr.getMessage(), mapErr.getCause()); - } + final TextException textErr = tryException(TextException.class, thrown); - final ModuleLoadException moduleErr = tryException(ModuleLoadException.class, thrown); - if (moduleErr != null) { - final Class module = moduleErr.getModule(); - return format( - (module == null ? ModuleLoadException.class : module).getSimpleName(), - moduleErr.getMessage(), - moduleErr.getCause()); + Throwable cause = thrown.getCause(); + String message = thrown.getMessage(); + if (textErr != null) { + cause = null; + message = textErr.getLocalizedMessage(); + } else if (xmlErr != null) { + cause = xmlErr.getCause(); + message = xmlErr.getMessage(); + } else if (mapErr != null) { + cause = mapErr.getCause(); + message = mapErr.getMessage(); + } else if (moduleErr != null) { + cause = moduleErr.getCause(); + message = moduleErr.getMessage(); } - return null; + return format(location, message, cause); } private String format( diff --git a/util/src/main/i18n/templates/misc.properties b/util/src/main/i18n/templates/misc.properties index 0223ad31eb..d452913d43 100644 --- a/util/src/main/i18n/templates/misc.properties +++ b/util/src/main/i18n/templates/misc.properties @@ -221,5 +221,9 @@ type.team = team type.settingkey = setting +type.uuid = uuid + +type.localdate = (yyyy-mm-dd) date + # {0} = a URL (e.g. "https://oc.tc") chat.clickLink = More info at {0} diff --git a/util/src/main/java/tc/oc/pgm/util/text/TextException.java b/util/src/main/java/tc/oc/pgm/util/text/TextException.java index 59985bec57..60ddf2f16c 100644 --- a/util/src/main/java/tc/oc/pgm/util/text/TextException.java +++ b/util/src/main/java/tc/oc/pgm/util/text/TextException.java @@ -56,6 +56,10 @@ public static TextException unknown(@Nullable Throwable cause) { return new TextException(cause, null, "error.unknown"); } + public static TextException invalidFormat(String text, Class type) { + return invalidFormat(text, type, null); + } + public static TextException invalidFormat(String text, Class type, @Nullable Throwable cause) { return invalidFormat(text, type, null, cause); } diff --git a/util/src/main/java/tc/oc/pgm/util/text/TextParser.java b/util/src/main/java/tc/oc/pgm/util/text/TextParser.java index 1a8719ece5..6723120fe0 100644 --- a/util/src/main/java/tc/oc/pgm/util/text/TextParser.java +++ b/util/src/main/java/tc/oc/pgm/util/text/TextParser.java @@ -1,7 +1,6 @@ package tc.oc.pgm.util.text; import static com.google.common.base.Preconditions.checkNotNull; -import static net.kyori.adventure.text.Component.text; import static tc.oc.pgm.util.text.TextException.invalidFormat; import static tc.oc.pgm.util.text.TextException.outOfRange; import static tc.oc.pgm.util.text.TextException.unknown; @@ -17,8 +16,11 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.time.Duration; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoUnit; +import java.util.UUID; import java.util.logging.Level; import java.util.regex.Pattern; import net.kyori.adventure.text.Component; @@ -56,7 +58,7 @@ public static boolean parseBoolean(String text) throws TextException { if (YES.matcher(text).matches()) return true; if (NO.matcher(text).matches()) return false; - throw invalidFormat(text, boolean.class, null); + throw invalidFormat(text, boolean.class); } /** @@ -225,7 +227,7 @@ public static Vector parseVector3d(String text, Range rangeXZ, Range range) throws Tex final int size = components.length; if (size < 1 || size > 3) { - throw invalidFormat(text, Version.class, null); + throw invalidFormat(text, Version.class); } final int major = parseInteger(components[0], NONNEG); @@ -365,6 +367,40 @@ public static > E parseEnum(String text, Class type) throws return parseEnum(text, type, null, false); } + /** + * Parses text into a UUID. + * + * @param text The text. + * @return A UUID. + * @throws TextException If the text is invalid. + */ + public static UUID parseUuid(String text) throws TextException { + checkNotNull(text, "cannot parse uuid from null"); + + try { + return UUID.fromString(text); + } catch (IllegalArgumentException e) { + throw invalidFormat(text, UUID.class, e); + } + } + + /** + * Parses text into a date. + * + * @param text The text. + * @return A date. + * @throws TextException If the text is invalid. + */ + public static LocalDate parseDate(String text) throws TextException { + checkNotNull(text, "cannot parse date from null"); + + try { + return LocalDate.parse(text, DateTimeFormatter.ISO_LOCAL_DATE); + } catch (DateTimeParseException e) { + throw invalidFormat(text, LocalDate.class, e); + } + } + /** * Parses text into a text component. * @@ -431,7 +467,7 @@ public static URI parseUri(String text) throws TextException { checkNotNull(text, "cannot parse uri from null"); if (text.trim().isEmpty()) { - throw invalidFormat(text, URI.class, null); + throw invalidFormat(text, URI.class); } try { @@ -463,7 +499,7 @@ public static Connection parseSqlConnection(String text) throws TextException { final String scheme = uri.getScheme(); try { if (scheme == null || scheme.isEmpty()) { - throw invalidFormat(text, URI.class, null); + throw invalidFormat(text, URI.class); } else if (scheme.startsWith("sqlite")) { Class.forName("org.sqlite.JDBC"); } else if (scheme.startsWith("mysql")) { diff --git a/util/src/main/java/tc/oc/pgm/util/xml/InvalidXMLException.java b/util/src/main/java/tc/oc/pgm/util/xml/InvalidXMLException.java index 924a0f3666..274c7a9fe3 100644 --- a/util/src/main/java/tc/oc/pgm/util/xml/InvalidXMLException.java +++ b/util/src/main/java/tc/oc/pgm/util/xml/InvalidXMLException.java @@ -37,6 +37,10 @@ protected InvalidXMLException( this.column = column > 0 ? column : this.node != null ? this.node.getColumn() : 0; } + public InvalidXMLException(@Nullable Node node, Throwable cause) { + this("", node, cause); + } + public InvalidXMLException(String message, @Nullable Node node, Throwable cause) { this(message, node, null, null, 0, 0, 0, cause); } diff --git a/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java b/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java index 75a9711336..1d88dca59c 100644 --- a/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java +++ b/util/src/main/java/tc/oc/pgm/util/xml/XMLUtils.java @@ -1,14 +1,10 @@ package tc.oc.pgm.util.xml; -import static net.kyori.adventure.text.Component.text; - import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.*; import java.time.Duration; import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -183,17 +179,11 @@ public static Attribute getRequiredAttribute(Element el, String... aliases) } private static Boolean parseBoolean(Node node, String value) throws InvalidXMLException { - if ("true".equalsIgnoreCase(value) - || "yes".equalsIgnoreCase(value) - || "on".equalsIgnoreCase(value)) { - return true; - } - if ("false".equalsIgnoreCase(value) - || "no".equalsIgnoreCase(value) - || "off".equalsIgnoreCase(value)) { - return false; + try { + return TextParser.parseBoolean(value); + } catch (TextException e) { + throw new InvalidXMLException(node, e); } - throw new InvalidXMLException("invalid boolean value", node); } public static Boolean parseBoolean(Node node) throws InvalidXMLException { @@ -440,58 +430,6 @@ public static > Range parseNumericRange( } } - /** - * Parse a numeric range from attributes on the given element specifying the bounds of the range, - * specifically: - * - *

gt gte lt lte - */ - public static > Range parseNumericRange( - Element el, Class type) throws InvalidXMLException { - Attribute lt = el.getAttribute("lt"); - Attribute lte = el.getAttribute("lte"); - Attribute gt = el.getAttribute("gt"); - Attribute gte = el.getAttribute("gte"); - - if (lt != null && lte != null) - throw new InvalidXMLException("Conflicting upper bound for numeric range", el); - if (gt != null && gte != null) - throw new InvalidXMLException("Conflicting lower bound for numeric range", el); - - BoundType lowerBoundType, upperBoundType; - T lowerBound, upperBound; - - if (gt != null) { - lowerBound = parseNumber(gt, type, (T) null); - lowerBoundType = BoundType.OPEN; - } else { - lowerBound = parseNumber(gte, type, (T) null); - lowerBoundType = BoundType.CLOSED; - } - - if (lt != null) { - upperBound = parseNumber(lt, type, (T) null); - upperBoundType = BoundType.OPEN; - } else { - upperBound = parseNumber(lte, type, (T) null); - upperBoundType = BoundType.CLOSED; - } - - if (lowerBound == null) { - if (upperBound == null) { - return Range.all(); - } else { - return Range.upTo(upperBound, upperBoundType); - } - } else { - if (upperBound == null) { - return Range.downTo(lowerBound, lowerBoundType); - } else { - return Range.range(lowerBound, lowerBoundType, upperBound, upperBoundType); - } - } - } - public static Duration parseDuration(Node node, Duration def) throws InvalidXMLException { if (node == null) { return def; @@ -499,7 +437,7 @@ public static Duration parseDuration(Node node, Duration def) throws InvalidXMLE try { return TextParser.parseDuration(node.getValueNormalize()); } catch (TextException e) { - throw new InvalidXMLException("invalid time format", node, e); + throw new InvalidXMLException(node, e); } } @@ -529,7 +467,6 @@ public static Duration parseTickDuration(Node node, String text) throws InvalidX } public static Duration parseSecondDuration(Node node, String text) throws InvalidXMLException { - if ("oo".equals(text)) return TimeUtils.INFINITE_DURATION; try { return Duration.ofSeconds(Integer.parseInt(text)); } catch (NumberFormatException e) { @@ -829,9 +766,9 @@ public static PotionEffect parseCompactPotionEffect(Node node, String text) public static > T parseEnum( Node node, String text, Class type, String readableType) throws InvalidXMLException { try { - return Enum.valueOf(type, text.trim().toUpperCase().replace(' ', '_')); - } catch (IllegalArgumentException ex) { - throw new InvalidXMLException("Unknown " + readableType + " '" + text + "'", node); + return TextParser.parseEnum(text, type); + } catch (TextException e) { + throw new InvalidXMLException(node, e); } } @@ -891,11 +828,10 @@ public static String getNullableAttribute(Element el, String... attrs) { public static UUID parseUuid(@Nullable Node node) throws InvalidXMLException { if (node == null) return null; - String raw = node.getValue(); try { - return UUID.fromString(raw); - } catch (IllegalArgumentException e) { - throw new InvalidXMLException("Invalid UUID format (must be 8-4-4-4-12)", node, e); + return TextParser.parseUuid(node.getValueNormalize()); + } catch (TextException e) { + throw new InvalidXMLException(node, e); } } @@ -921,12 +857,6 @@ public static Skin parseUnsignedSkin(@Nullable Node node) throws InvalidXMLExcep return new Skin(data, null); } - /** Guess if the given text is a JSON object by looking for the curly braces at either finish */ - public static boolean looksLikeJson(String text) { - text = text.trim(); - return text.startsWith("{") && text.endsWith("}"); - } - /** * Parse a piece of formatted text, which can be either plain text with legacy formatting codes, * or JSON chat components. @@ -1076,11 +1006,10 @@ public static Map.Entry parseAttributeModifier(Elemen } public static GameMode parseGameMode(Node node, String text) throws InvalidXMLException { - text = text.trim(); try { - return GameMode.valueOf(text.toUpperCase()); - } catch (IllegalArgumentException e) { - throw new InvalidXMLException("Unknown game-mode '" + text + "'", node); + return TextParser.parseEnum(text, GameMode.class); + } catch (TextException e) { + throw new InvalidXMLException(node, e); } } @@ -1095,29 +1024,20 @@ public static GameMode parseGameMode(Node node, GameMode def) throws InvalidXMLE public static Version parseSemanticVersion(Node node) throws InvalidXMLException { if (node == null) return null; - String[] parts = node.getValueNormalize().split("\\.", 3); - if (parts.length < 1 || parts.length > 3) { - throw new InvalidXMLException( - "Version must be 1 to 3 whole numbers, separated by periods", node); + try { + return TextParser.parseVersion(node.getValueNormalize()); + } catch (TextException e) { + throw new InvalidXMLException(node, e); } - - int major = parseNumber(node, parts[0], Integer.class); - int minor = parts.length < 2 ? 0 : parseNumber(node, parts[1], Integer.class); - int patch = parts.length < 3 ? 0 : parseNumber(node, parts[2], Integer.class); - - return new Version(major, minor, patch); } public static LocalDate parseDate(Node node) throws InvalidXMLException { if (node == null) return null; - DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE; - try { - return LocalDate.parse(node.getValueNormalize(), formatter); - } catch (DateTimeParseException exception) { - throw new InvalidXMLException( - "Invalid date '" + node.getValueNormalize() + "', expected format 'YYYY-MM-DD'", node); + return TextParser.parseDate(node.getValueNormalize()); + } catch (TextException e) { + throw new InvalidXMLException(node, e); } } } diff --git a/util/src/test/java/tc/oc/pgm/util/text/TextParserTest.java b/util/src/test/java/tc/oc/pgm/util/text/TextParserTest.java index 7604020395..d7bc0a489e 100644 --- a/util/src/test/java/tc/oc/pgm/util/text/TextParserTest.java +++ b/util/src/test/java/tc/oc/pgm/util/text/TextParserTest.java @@ -9,6 +9,8 @@ import java.sql.Connection; import java.sql.SQLException; import java.time.Duration; +import java.time.LocalDate; +import java.util.UUID; import org.bukkit.ChatColor; import org.bukkit.util.Vector; import org.junit.jupiter.params.ParameterizedTest; @@ -240,6 +242,7 @@ void testParseSqlConnection(String text) throws SQLException { assertFalse(connection.isClosed()); connection.close(); + assertTrue(connection.isClosed()); } @ParameterizedTest @@ -261,4 +264,36 @@ void testParseSqlConnectionInvalid(String text) { void testParseComponentLegacy(String text, String legacyText) { assertEquals(legacyText, parseComponentLegacy(text)); } + + @ParameterizedTest + @CsvSource({ + "dad8b95c-cf6a-44df-982e-8c8dd70201e0", + "211fa6b9-5614-340a-a0f0-2a6691b56065", + "00000000-0000-0000-0000-000000000000" + }) + void testParseUuid(String text) throws TextException { + assertEquals(UUID.fromString(text), parseUuid(text)); + } + + @ParameterizedTest + @CsvSource({"-", "notauuid", "dad8b95ccf6a44df982e8c8dd70201e0"}) + void testParseUuidInvalid(String text) { + assertEquals( + "error.invalidFormat", + assertThrows(TextException.class, () -> parseUuid(text)).getMessage()); + } + + @ParameterizedTest + @CsvSource({"2000-01-01", "1984-11-11", "2009-09-09"}) + void testParseDate(String text) throws TextException { + assertEquals(LocalDate.parse(text), parseDate(text)); + } + + @ParameterizedTest + @CsvSource({"01-01-01", "date", "2021-05"}) + void testParseDateInvalid(String text) { + assertEquals( + "error.invalidFormat", + assertThrows(TextException.class, () -> parseDate(text)).getMessage()); + } }