From a5dc70a3b56bd84974f6bb0cca0fcca438d089c9 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Tue, 2 Aug 2022 23:22:08 -0400 Subject: [PATCH 1/3] Refactor extension description --- .../api/extension/ExtensionDescription.java | 26 ++++++- .../extension/GeyserExtensionDescription.java | 76 ++++++++++++------- .../extension/GeyserExtensionLoader.java | 35 +++------ 3 files changed, 82 insertions(+), 55 deletions(-) diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java index e77411144..3969bb65f 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionDescription.java @@ -50,13 +50,35 @@ public interface ExtensionDescription { @NonNull String main(); + /** + * Gets the extension's major api version + * + * @return the extension's major api version + */ + int majorApiVersion(); + + /** + * Gets the extension's minor api version + * + * @return the extension's minor api version + */ + int minorApiVersion(); + + /** + * Gets the extension's patch api version + * + * @return the extension's patch api version + */ + int patchApiVersion(); + /** * Gets the extension's api version * * @return the extension's api version */ - @NonNull - String apiVersion(); + default String apiVersion() { + return majorApiVersion() + "." + minorApiVersion() + "." + patchApiVersion(); + } /** * Gets the extension's description diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index eaf29a819..797b43a81 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,53 +25,75 @@ package org.geysermc.geyser.extension; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; -import org.yaml.snakeyaml.DumperOptions; +import org.geysermc.geyser.text.GeyserLocale; import org.yaml.snakeyaml.Yaml; import java.io.Reader; import java.util.*; +import java.util.regex.Pattern; -public record GeyserExtensionDescription(String name, String main, String apiVersion, String version, List authors) implements ExtensionDescription { +public record GeyserExtensionDescription(@NonNull String name, + @NonNull String main, + int majorApiVersion, + int minorApiVersion, + int patchApiVersion, + @NonNull String version, + @NonNull List authors) implements ExtensionDescription { + + private static final Yaml YAML = new Yaml(); + public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]*$"); + public static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); + + @NonNull @SuppressWarnings("unchecked") public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { - DumperOptions dumperOptions = new DumperOptions(); - dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - - Yaml yaml = new Yaml(dumperOptions); - Map yamlMap = yaml.loadAs(reader, LinkedHashMap.class); - - String name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); - if (name.isBlank()) { - throw new InvalidDescriptionException("Invalid extension name, cannot be empty"); + Map map; + try { + map = YAML.loadAs(reader, HashMap.class); + } catch (Exception e) { + throw new InvalidDescriptionException(e); } - name = name.replace(" ", "_"); - String version = String.valueOf(yamlMap.get("version")); - String main = (String) yamlMap.get("main"); - String apiVersion; - - Object api = yamlMap.get("api"); - if (api instanceof String) { - apiVersion = (String) api; - } else { - throw new InvalidDescriptionException("Invalid api version format, should be a string: major.minor.patch"); + String name = require(map, "name"); + if (!NAME_PATTERN.matcher(name).matches()) { + throw new InvalidDescriptionException("Invalid extension name, must match: " + NAME_PATTERN.pattern()); } + String version = String.valueOf(map.get("version")); + String main = require(map, "main"); + + String apiVersion = require(map, "api"); + if (!API_VERSION_PATTERN.matcher(apiVersion).matches()) { + throw new InvalidDescriptionException(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion)); + } + String[] api = apiVersion.split("\\."); + int majorApi = Integer.parseUnsignedInt(api[0]); + int minorApi = Integer.parseUnsignedInt(api[1]); + int patchApi = Integer.parseUnsignedInt(api[2]); List authors = new ArrayList<>(); - if (yamlMap.containsKey("author")) { - authors.add((String) yamlMap.get("author")); + if (map.containsKey("author")) { + authors.add(String.valueOf(map.get("author"))); } - - if (yamlMap.containsKey("authors")) { + if (map.containsKey("authors")) { try { - authors.addAll((Collection) yamlMap.get("authors")); + authors.addAll((Collection) map.get("authors")); } catch (Exception e) { throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); } } - return new GeyserExtensionDescription(name, main, apiVersion, version, authors); + return new GeyserExtensionDescription(name, main, majorApi, minorApi, patchApi, version, authors); + } + + @NonNull + private static String require(Map desc, String key) throws InvalidDescriptionException { + Object value = desc.get(key); + if (value instanceof String) { + return (String) value; + } + throw new InvalidDescriptionException("Extension description is missing string property '" + key + "'"); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java index 55f018ddb..fcb718dec 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -39,25 +39,24 @@ import org.geysermc.geyser.extension.event.GeyserExtensionEventBus; import org.geysermc.geyser.text.GeyserLocale; import java.io.IOException; +import java.io.Reader; import java.nio.file.*; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; import java.util.regex.Pattern; import java.util.stream.Stream; @RequiredArgsConstructor public class GeyserExtensionLoader extends ExtensionLoader { private static final Path EXTENSION_DIRECTORY = Paths.get("extensions"); - private static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); private static final Pattern[] EXTENSION_FILTERS = new Pattern[] { Pattern.compile("^.+\\.jar$") }; private final Object2ReferenceMap> classes = new Object2ReferenceOpenHashMap<>(); private final Map classLoaders = new HashMap<>(); private final Map extensionContainers = new HashMap<>(); - public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException, InvalidDescriptionException { + public GeyserExtensionContainer loadExtension(Path path, GeyserExtensionDescription description) throws InvalidExtensionException { if (path == null) { throw new InvalidExtensionException("Path is null"); } @@ -94,7 +93,9 @@ public class GeyserExtensionLoader extends ExtensionLoader { Map environment = new HashMap<>(); try (FileSystem fileSystem = FileSystems.newFileSystem(path, environment, null)) { Path extensionYml = fileSystem.getPath("extension.yml"); - return GeyserExtensionDescription.fromYaml(Files.newBufferedReader(extensionYml)); + try (Reader reader = Files.newBufferedReader(extensionYml)) { + return GeyserExtensionDescription.fromYaml(reader); + } } catch (IOException ex) { throw new InvalidDescriptionException("Failed to load extension description for " + path, ex); } @@ -149,9 +150,6 @@ public class GeyserExtensionLoader extends ExtensionLoader { try { GeyserExtensionDescription description = this.extensionDescription(path); - if (description == null) { - return; - } String name = description.name(); if (extensions.containsKey(name) || extensionManager.extension(name) != null) { @@ -159,30 +157,15 @@ public class GeyserExtensionLoader extends ExtensionLoader { return; } - int majorVersion = Geyser.api().majorApiVersion(); - int minorVersion = Geyser.api().minorApiVersion(); - - try { - // Check the format: majorVersion.minorVersion.patch - if (!API_VERSION_PATTERN.matcher(description.apiVersion()).matches()) { - throw new IllegalArgumentException(); - } - } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, majorVersion + "." + minorVersion)); - return; - } - - String[] versionArray = description.apiVersion().split("\\."); - // Completely different API version - if (Integer.parseInt(versionArray[0]) != majorVersion) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); + if (description.majorApiVersion() != Geyser.api().majorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); return; } // If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > minorVersion) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, majorVersion + "." + minorVersion)); + if (description.minorApiVersion() > Geyser.api().minorApiVersion()) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, description.apiVersion())); return; } From aa7d0f4a57b2b93acc69aab0e2f1704c2e0e188f Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 3 Aug 2022 00:20:27 -0400 Subject: [PATCH 2/3] Use class for reading extension.yml --- .../extension/GeyserExtensionDescription.java | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 797b43a81..8fab4e338 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,14 +25,18 @@ package org.geysermc.geyser.extension; +import lombok.Getter; +import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.geysermc.geyser.text.GeyserLocale; import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor; import java.io.Reader; import java.util.*; +import java.util.function.Supplier; import java.util.regex.Pattern; public record GeyserExtensionDescription(@NonNull String name, @@ -43,28 +47,27 @@ public record GeyserExtensionDescription(@NonNull String name, @NonNull String version, @NonNull List authors) implements ExtensionDescription { - private static final Yaml YAML = new Yaml(); + private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader())); public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]*$"); public static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); @NonNull - @SuppressWarnings("unchecked") public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { - Map map; + Source source; try { - map = YAML.loadAs(reader, HashMap.class); + source = YAML.loadAs(reader, Source.class); } catch (Exception e) { throw new InvalidDescriptionException(e); } - String name = require(map, "name"); + String name = require(source::getName, "name"); if (!NAME_PATTERN.matcher(name).matches()) { throw new InvalidDescriptionException("Invalid extension name, must match: " + NAME_PATTERN.pattern()); } - String version = String.valueOf(map.get("version")); - String main = require(map, "main"); + String version = String.valueOf(source.version); + String main = require(source::getMain, "main"); - String apiVersion = require(map, "api"); + String apiVersion = require(source::getApi, "api"); if (!API_VERSION_PATTERN.matcher(apiVersion).matches()) { throw new InvalidDescriptionException(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion)); } @@ -74,26 +77,33 @@ public record GeyserExtensionDescription(@NonNull String name, int patchApi = Integer.parseUnsignedInt(api[2]); List authors = new ArrayList<>(); - if (map.containsKey("author")) { - authors.add(String.valueOf(map.get("author"))); + if (source.author != null) { + authors.add(source.author); } - if (map.containsKey("authors")) { - try { - authors.addAll((Collection) map.get("authors")); - } catch (Exception e) { - throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); - } + if (source.authors != null) { + authors.addAll(source.authors); } return new GeyserExtensionDescription(name, main, majorApi, minorApi, patchApi, version, authors); } @NonNull - private static String require(Map desc, String key) throws InvalidDescriptionException { - Object value = desc.get(key); - if (value instanceof String) { - return (String) value; + private static String require(Supplier supplier, String name) throws InvalidDescriptionException { + String value = supplier.get(); + if (value == null) { + throw new InvalidDescriptionException("Extension description is missing string property '" + name + "'"); } - throw new InvalidDescriptionException("Extension description is missing string property '" + key + "'"); + return value; + } + + @Getter + @Setter + public static class Source { + String name; + String main; + String api; + String version; + String author; + List authors; } } From 36ef23b24e50590fef2b63b52478a86ea2008763 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Wed, 3 Aug 2022 00:30:22 -0400 Subject: [PATCH 3/3] Don't allow empty extension name --- .../geysermc/geyser/extension/GeyserExtensionDescription.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java index 8fab4e338..010c1430c 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -48,7 +48,7 @@ public record GeyserExtensionDescription(@NonNull String name, @NonNull List authors) implements ExtensionDescription { private static final Yaml YAML = new Yaml(new CustomClassLoaderConstructor(Source.class.getClassLoader())); - public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]*$"); + public static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z_.-]+$"); public static final Pattern API_VERSION_PATTERN = Pattern.compile("^\\d+\\.\\d+\\.\\d+$"); @NonNull