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] 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; }