diff --git a/api/geyser/pom.xml b/api/geyser/pom.xml index 89349e8ac..5c598f65b 100644 --- a/api/geyser/pom.xml +++ b/api/geyser/pom.xml @@ -6,6 +6,7 @@ org.geysermc api-parent 2.0.0-SNAPSHOT + ../pom.xml 4.0.0 @@ -14,6 +15,8 @@ 16 16 + + 4.9.3 @@ -23,6 +26,12 @@ 3.19.0 provided + + net.kyori + adventure-api + ${adventure.version} + compile + org.geysermc base-api diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java index 074918881..d5fd09381 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/GeyserApi.java @@ -27,8 +27,10 @@ package org.geysermc.geyser.api; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.geysermc.api.Geyser; import org.geysermc.api.GeyserApiBase; import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.extension.ExtensionManager; import java.util.List; import java.util.UUID; @@ -78,4 +80,20 @@ public interface GeyserApi extends GeyserApiBase { */ @NonNull List onlineConnections(); + + /** + * Gets the {@link ExtensionManager}. + * + * @return the extension manager + */ + ExtensionManager extensionManager(); + + /** + * Gets the current {@link GeyserApiBase} instance. + * + * @return the current geyser api instance + */ + static GeyserApi api() { + return Geyser.api(GeyserApi.class); + } } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java new file mode 100644 index 000000000..097cabdc3 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/Extension.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.extension; + +import org.geysermc.api.GeyserApiBase; +import org.geysermc.geyser.api.GeyserApi; + +import java.nio.file.Path; + +/** + * Represents an extension within Geyser. + */ +public interface Extension { + + /** + * Called when the extension is loaded + */ + default void onLoad() { + } + + /** + * Called when the extension is enabled + */ + default void onEnable() { + } + + /** + * Called when the extension is disabled + */ + default void onDisable() { + } + + /** + * Gets if the extension is enabled + * + * @return true if the extension is enabled + */ + default boolean isEnabled() { + return this.extensionLoader().isEnabled(this); + } + + /** + * Enables or disables the extension + * + * @param enabled if the extension should be enabled + */ + default void setEnabled(boolean enabled) { + this.extensionLoader().setEnabled(this, enabled); + } + + /** + * Gets the extension's data folder + * + * @return the extension's data folder + */ + default Path dataFolder() { + return this.extensionLoader().dataFolder(this); + } + + /** + * Gets the {@link ExtensionManager}. + * + * @return the extension manager + */ + default ExtensionManager extensionManager() { + return this.geyserApi().extensionManager(); + } + + /** + * Gets the extension's name + * + * @return the extension's name + */ + default String name() { + return this.description().name(); + } + + /** + * Gets this extension's {@link ExtensionDescription}. + * + * @return the extension's description + */ + default ExtensionDescription description() { + return this.extensionLoader().description(this); + } + + /** + * Gets the extension's logger + * + * @return the extension's logger + */ + default ExtensionLogger logger() { + return this.extensionLoader().logger(this); + } + + /** + * Gets the {@link ExtensionLoader}. + * + * @return the extension loader + */ + default ExtensionLoader extensionLoader() { + return this.extensionManager().extensionLoader(this); + } + + /** + * Gets the {@link GeyserApiBase} instance + * + * @return the geyser api instance + */ + default GeyserApi geyserApi() { + return GeyserApi.api(); + } +} 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 487df3926..e77411144 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 @@ -25,17 +25,21 @@ package org.geysermc.geyser.api.extension; +import org.checkerframework.checker.nullness.qual.NonNull; + import java.util.List; /** * This is the Geyser extension description */ public interface ExtensionDescription { + /** * Gets the extension's name * * @return the extension's name */ + @NonNull String name(); /** @@ -43,6 +47,7 @@ public interface ExtensionDescription { * * @return the extension's main class */ + @NonNull String main(); /** @@ -50,6 +55,7 @@ public interface ExtensionDescription { * * @return the extension's api version */ + @NonNull String apiVersion(); /** @@ -57,6 +63,7 @@ public interface ExtensionDescription { * * @return the extension's description */ + @NonNull String version(); /** @@ -64,5 +71,6 @@ public interface ExtensionDescription { * * @return the extension's authors */ + @NonNull List authors(); } diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java index 1301493d5..651afd9eb 100644 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionLoader.java @@ -25,52 +25,72 @@ package org.geysermc.geyser.api.extension; -import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; -import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; -import java.io.File; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.nio.file.Path; /** * The extension loader is responsible for loading, unloading, enabling and disabling extensions */ -public interface ExtensionLoader { - /** - * Loads an extension from a given file - * - * @param file the file to load the extension from - * @return the loaded extension - * @throws InvalidExtensionException - */ - GeyserExtension loadExtension(File file) throws InvalidExtensionException; +public abstract class ExtensionLoader { /** - * Gets an extension's description from a given file + * Gets if the given {@link Extension} is enabled. * - * @param file the file to get the description from - * @return the extension's description - * @throws InvalidDescriptionException + * @param extension the extension + * @return if the extension is enabled */ - ExtensionDescription extensionDescription(File file) throws InvalidDescriptionException; + protected abstract boolean isEnabled(@NonNull Extension extension); /** - * Gets a class by its name from the extension's classloader - * - * @param name the name of the class - * @return the class - * @throws ClassNotFoundException - */ - Class classByName(final String name) throws ClassNotFoundException; - - /** - * Enables an extension + * Sets if the given {@link Extension} is enabled. * * @param extension the extension to enable + * @param enabled if the extension should be enabled */ - void enableExtension(GeyserExtension extension); + protected abstract void setEnabled(@NonNull Extension extension, boolean enabled); /** - * Disables an extension + * Gets the given {@link Extension}'s data folder. * - * @param extension the extension to disable + * @param extension the extension + * @return the data folder of the given extension */ - void disableExtension(GeyserExtension extension); -} + @NonNull + protected abstract Path dataFolder(@NonNull Extension extension); + + /** + * Gets the given {@link Extension}'s {@link ExtensionDescription}. + * + * @param extension the extension + * @return the description of the given extension + */ + @NonNull + protected abstract ExtensionDescription description(@NonNull Extension extension); + + /** + * Gets the {@link ExtensionLogger} for the given {@link Extension}. + * + * @param extension the extension + * @return the extension logger for the given extension + */ + @NonNull + protected abstract ExtensionLogger logger(@NonNull Extension extension); + + /** + * Loads all extensions. + * + * @param extensionManager the extension manager + */ + protected abstract void loadAllExtensions(@NonNull ExtensionManager extensionManager); + + /** + * Registers the given {@link Extension} with the given {@link ExtensionManager}. + * + * @param extension the extension + * @param extensionManager the extension manager + */ + protected void register(@NonNull Extension extension, @NonNull ExtensionManager extensionManager) { + extensionManager.register(extension, this); + } +} \ No newline at end of file diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java new file mode 100644 index 000000000..65387a8c7 --- /dev/null +++ b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/ExtensionManager.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.extension; + +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; +import java.util.Map; + +/** + * Manages Geyser {@link Extension}s + */ +public abstract class ExtensionManager { + + /** + * Gets an extension with the given name. + * + * @param name the name of the extension + * @return an extension with the given name + */ + @Nullable + public abstract Extension extension(@NotNull String name); + + /** + * Enables the given {@link Extension}. + * + * @param extension the extension to enable + */ + public abstract void enable(@NonNull Extension extension); + + /** + * Disables the given {@link Extension}. + * + * @param extension the extension to disable + */ + public abstract void disable(@NonNull Extension extension); + + /** + * Gets the {@link ExtensionLoader} responsible for loading + * the given {@link Extension}. + * + * @return the extension loader for loading the given extension + */ + @Nullable + public abstract ExtensionLoader extensionLoader(@NotNull Extension extension); + + /** + * Gets all the {@link Extension}s currently loaded. + * + * @return all the extensions currently loaded + */ + @NonNull + public abstract Collection extensions(); + + /** + * Gets the {@link ExtensionLoader} with the given identifier. + * + * @param identifier the identifier + * @return the extension loader at the given identifier + */ + @Nullable + public abstract ExtensionLoader extensionLoader(@NonNull Key identifier); + + /** + * Registers an {@link ExtensionLoader} with the given identifier. + * + * @param identifier the identifier + * @param extensionLoader the extension loader + */ + public abstract void registerExtensionLoader(@NonNull Key identifier, @NotNull ExtensionLoader extensionLoader); + + /** + * Gets all the currently registered {@link ExtensionLoader}s. + * + * @return all the currently registered extension loaders + */ + @NonNull + public abstract Map extensionLoaders(); + + /** + * Registers an {@link Extension} with the given {@link ExtensionLoader}. + * + * @param extension the extension + * @param loader the loader + */ + public abstract void register(@NotNull Extension extension, @NotNull ExtensionLoader loader); + + /** + * Loads all extensions from the given {@link ExtensionLoader}. + */ + protected final void loadAllExtensions(@NotNull ExtensionLoader extensionLoader) { + extensionLoader.loadAllExtensions(this); + } +} diff --git a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java b/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java deleted file mode 100644 index bd53bafd3..000000000 --- a/api/geyser/src/main/java/org/geysermc/geyser/api/extension/GeyserExtension.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * @author GeyserMC - * @link https://github.com/GeyserMC/Geyser - */ - -package org.geysermc.geyser.api.extension; - -import org.geysermc.api.GeyserApiBase; -import java.io.*; -import java.net.URL; -import java.net.URLConnection; - -/** - * This class is to be extended by a Geyser extension - */ -public class GeyserExtension { - private boolean initialized = false; - private boolean enabled = false; - private File file = null; - private File dataFolder = null; - private ClassLoader classLoader = null; - private ExtensionLoader loader = null; - private ExtensionLogger logger = null; - private ExtensionDescription description = null; - private GeyserApiBase api = null; - - /** - * Called when the extension is loaded - */ - public void onLoad() { - - } - - /** - * Called when the extension is enabled - */ - public void onEnable() { - - } - - /** - * Called when the extension is disabled - */ - public void onDisable() { - - } - - /** - * Gets if the extension is enabled - * - * @return true if the extension is enabled - */ - public boolean isEnabled() { - return this.enabled; - } - - /** - * Enables or disables the extension - */ - public void setEnabled(boolean value) { - if (this.enabled != value) { - this.enabled = value; - if (this.enabled) { - onEnable(); - } else { - onDisable(); - } - } - } - - /** - * Gets the extension's data folder - * - * @return the extension's data folder - */ - public File dataFolder() { - return this.dataFolder; - } - - /** - * Gets the extension's description - * - * @return the extension's description - */ - public ExtensionDescription description() { - return this.description; - } - - /** - * Gets the extension's name - * - * @return the extension's name - */ - public String name() { - return this.description.name(); - } - - public void init(GeyserApiBase api, ExtensionLoader loader, ExtensionLogger logger, ExtensionDescription description, File dataFolder, File file) { - if (!this.initialized) { - this.initialized = true; - this.file = file; - this.dataFolder = dataFolder; - this.classLoader = this.getClass().getClassLoader(); - this.loader = loader; - this.logger = logger; - this.description = description; - this.api = api; - } - } - - /** - * Gets a resource from the extension jar file - * - * @param filename the file name - * @return the input stream - */ - public InputStream getResource(String filename) { - if (filename == null) { - throw new IllegalArgumentException("Filename cannot be null"); - } - - try { - URL url = this.classLoader.getResource(filename); - - if (url == null) { - return null; - } - - URLConnection connection = url.openConnection(); - connection.setUseCaches(false); - return connection.getInputStream(); - } catch (IOException ex) { - return null; - } - } - - /** - * Saves a resource from the extension jar file to the extension's data folder - * - * @param filename the file name - * @param replace whether to replace the file if it already exists - */ - public void saveResource(String filename, boolean replace) { - if (filename == null || filename.equals("")) { - throw new IllegalArgumentException("ResourcePath cannot be null or empty"); - } - - filename = filename.replace('\\', '/'); - InputStream in = getResource(filename); - if (in == null) { - throw new IllegalArgumentException("The embedded resource '" + filename + "' cannot be found in " + file); - } - - File outFile = new File(dataFolder, filename); - int lastIndex = filename.lastIndexOf('/'); - File outDir = new File(dataFolder, filename.substring(0, Math.max(lastIndex, 0))); - - if (!outDir.exists()) { - outDir.mkdirs(); - } - - try { - if (!outFile.exists() || replace) { - OutputStream out = new FileOutputStream(outFile); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - out.write(buf, 0, len); - } - out.close(); - in.close(); - } else { - this.logger.warning("Could not save " + outFile.getName() + " to " + outFile + " because " + outFile.getName() + " already exists."); - } - } catch (IOException ex) { - this.logger.severe("Could not save " + outFile.getName() + " to " + outFile, ex); - } - } - - /** - * Gets the extension's class loader - * - * @return the extension's class loader - */ - public ClassLoader classLoader() { - return this.classLoader; - } - - /** - * Gets the extension's loader - * - * @return the extension's loader - */ - public ExtensionLoader extensionLoader() { - return this.loader; - } - - /** - * Gets the extension's logger - * - * @return the extension's logger - */ - public ExtensionLogger logger() { - return this.logger; - } - - /** - * Gets the {@link GeyserApiBase} instance - * - * @return the {@link GeyserApiBase} instance - */ - public GeyserApiBase geyserApi() { - return this.api; - } -} diff --git a/api/pom.xml b/api/pom.xml index b3d0262ea..f66b982fb 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -7,6 +7,7 @@ org.geysermc geyser-parent 2.0.0-SNAPSHOT + ../pom.xml api-parent diff --git a/core/pom.xml b/core/pom.xml index 4da6bdbe0..4837660f9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -11,7 +11,6 @@ core - 4.9.3 8.5.2 2.12.4 4.1.66.Final diff --git a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java index 2fbcbaddd..6e740cb44 100644 --- a/core/src/main/java/org/geysermc/geyser/GeyserImpl.java +++ b/core/src/main/java/org/geysermc/geyser/GeyserImpl.java @@ -157,8 +157,9 @@ public class GeyserImpl implements GeyserApi { MessageTranslator.init(); MinecraftLocale.init(); - extensionManager = new GeyserExtensionManager(); - extensionManager.init(); + /* Load Extensions */ + this.extensionManager = new GeyserExtensionManager(); + this.extensionManager.init(); start(); @@ -203,7 +204,7 @@ public class GeyserImpl implements GeyserApi { ResourcePack.loadPacks(); - extensionManager.enableExtensions(); + this.extensionManager.enableExtensions(); if (platformType != PlatformType.STANDALONE && config.getRemote().getAddress().equals("auto")) { // Set the remote address to localhost since that is where we are always connecting @@ -465,7 +466,7 @@ public class GeyserImpl implements GeyserApi { ResourcePack.PACKS.clear(); - extensionManager.disableExtensions(); + this.extensionManager.disableExtensions(); bootstrap.getGeyserLogger().info(GeyserLocale.getLocaleStringLog("geyser.core.shutdown.done")); } @@ -488,6 +489,11 @@ public class GeyserImpl implements GeyserApi { return !"DEV".equals(GeyserImpl.VERSION); } + @Override + public GeyserExtensionManager extensionManager() { + return this.extensionManager; + } + public static GeyserImpl start(PlatformType platformType, GeyserBootstrap bootstrap) { if (instance == null) { return new GeyserImpl(platformType, bootstrap); @@ -518,10 +524,6 @@ public class GeyserImpl implements GeyserApi { return bootstrap.getWorldManager(); } - public GeyserExtensionManager getExtensionManager() { - return extensionManager; - } - public static GeyserImpl getInstance() { return instance; } diff --git a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java index 3377f7ee5..28e81d8e7 100644 --- a/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java +++ b/core/src/main/java/org/geysermc/geyser/dump/DumpInfo.java @@ -36,8 +36,8 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import lombok.AllArgsConstructor; import lombok.Getter; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.extension.GeyserExtension; -import org.geysermc.geyser.extension.GeyserExtensionManager; +import org.geysermc.geyser.api.GeyserApi; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.text.AsteriskSerializer; import org.geysermc.geyser.configuration.GeyserConfiguration; import org.geysermc.geyser.network.MinecraftProtocol; @@ -127,7 +127,7 @@ public class DumpInfo { this.flagsInfo = new FlagsInfo(); this.extensionInfo = new ArrayList<>(); - for (GeyserExtension extension : GeyserImpl.getInstance().getExtensionManager().getExtensions().values()) { + for (Extension extension : GeyserApi.api().extensionManager().extensions()) { this.extensionInfo.add(new ExtensionInfo(extension.isEnabled(), extension.name(), extension.description().version(), extension.description().apiVersion(), extension.description().main(), extension.description().authors())); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java index 67363a40f..426cd1de7 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionClassLoader.java @@ -25,24 +25,25 @@ package org.geysermc.geyser.extension; +import org.geysermc.geyser.api.extension.Extension; import org.geysermc.geyser.api.extension.ExtensionDescription; -import org.geysermc.geyser.api.extension.GeyserExtension; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; -import java.io.File; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Set; public class GeyserExtensionClassLoader extends URLClassLoader { private final GeyserExtensionLoader loader; - private final Map classes = new HashMap<>(); - public GeyserExtension extension; + private final Map> classes = new HashMap<>(); + private final Extension extension; - public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, File file) throws InvalidExtensionException, MalformedURLException { - super(new URL[] { file.toURI().toURL() }, parent); + public GeyserExtensionClassLoader(GeyserExtensionLoader loader, ClassLoader parent, ExtensionDescription description, Path path) throws InvalidExtensionException, MalformedURLException { + super(new URL[] { path.toUri().toURL() }, parent); this.loader = loader; try { @@ -53,15 +54,15 @@ public class GeyserExtensionClassLoader extends URLClassLoader { throw new InvalidExtensionException("Class " + description.main() + " not found, extension cannot be loaded", ex); } - Class extensionClass; + Class extensionClass; try { - extensionClass = jarClass.asSubclass(GeyserExtension.class); + extensionClass = jarClass.asSubclass(Extension.class); } catch (ClassCastException ex) { throw new InvalidExtensionException("Main class " + description.main() + " should extends GeyserExtension, but extends " + jarClass.getSuperclass().getSimpleName(), ex); } - extension = extensionClass.newInstance(); - } catch (IllegalAccessException ex) { + this.extension = extensionClass.getConstructor().newInstance(); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException ex) { throw new InvalidExtensionException("No public constructor", ex); } catch (InstantiationException ex) { throw new InvalidExtensionException("Abnormal extension type", ex); @@ -74,26 +75,28 @@ public class GeyserExtensionClassLoader extends URLClassLoader { } protected Class findClass(String name, boolean checkGlobal) throws ClassNotFoundException { - if (name.startsWith("org.geysermc.geyser.") || name.startsWith("org.geysermc.connector.") || name.startsWith("org.geysermc.platform.") || name.startsWith("org.geysermc.floodgate.") || name.startsWith("org.geysermc.api.") || name.startsWith("org.geysermc.processor.") || name.startsWith("net.minecraft.")) { + if (name.startsWith("org.geysermc.geyser.") || name.startsWith("net.minecraft.")) { throw new ClassNotFoundException(name); } - Class result = classes.get(name); + Class result = this.classes.get(name); if (result == null) { if (checkGlobal) { - result = loader.classByName(name); + result = this.loader.classByName(name); } + if (result == null) { result = super.findClass(name); if (result != null) { - loader.setClass(name, result); + this.loader.setClass(name, result); } } - classes.put(name, result); + + this.classes.put(name, result); } return result; } - Set getClasses() { - return classes.keySet(); + public Extension extension() { + return this.extension; } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java new file mode 100644 index 000000000..5b2e01842 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionContainer.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.extension; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Accessors; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionDescription; +import org.geysermc.geyser.api.extension.ExtensionLoader; +import org.geysermc.geyser.api.extension.ExtensionLogger; + +import java.nio.file.Path; + +@Accessors(fluent = true) +@Getter +@RequiredArgsConstructor +public class GeyserExtensionContainer { + private final Extension extension; + private final Path dataFolder; + private final ExtensionDescription description; + private final ExtensionLoader loader; + private final ExtensionLogger logger; + + @Getter(AccessLevel.NONE) protected boolean enabled; +} 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 f8a3d9bbe..5c8ff0ae2 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionDescription.java @@ -25,78 +25,52 @@ package org.geysermc.geyser.extension; +import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; -import java.io.InputStream; +import java.io.Reader; import java.util.*; -public class GeyserExtensionDescription implements org.geysermc.geyser.api.extension.ExtensionDescription { - private String name; - private String main; - private String api; - private String version; - private final List authors = new ArrayList<>(); - - public GeyserExtensionDescription(InputStream inputStream) throws InvalidDescriptionException { +public record GeyserExtensionDescription(String name, String main, String apiVersion, String version, List authors) implements ExtensionDescription { + @SuppressWarnings("unchecked") + public static GeyserExtensionDescription fromYaml(Reader reader) throws InvalidDescriptionException { DumperOptions dumperOptions = new DumperOptions(); dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); - Yaml yaml = new Yaml(dumperOptions); - this.loadMap(yaml.loadAs(inputStream, LinkedHashMap.class)); - } - private void loadMap(Map yamlMap) throws InvalidDescriptionException { - this.name = ((String) yamlMap.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); - if (this.name.equals("")) { + 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"); } - this.name = this.name.replace(" ", "_"); - this.version = String.valueOf(yamlMap.get("version")); - this.main = (String) yamlMap.get("main"); + + 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) { - this.api = (String) api; + apiVersion = (String) api; } else { - this.api = "0.0.0"; throw new InvalidDescriptionException("Invalid api version format, should be a string: major.minor.patch"); } + List authors = new ArrayList<>(); if (yamlMap.containsKey("author")) { - this.authors.add((String) yamlMap.get("author")); + authors.add((String) yamlMap.get("author")); } if (yamlMap.containsKey("authors")) { try { - this.authors.addAll((Collection) yamlMap.get("authors")); + authors.addAll((Collection) yamlMap.get("authors")); } catch (Exception e) { throw new InvalidDescriptionException("Invalid authors format, should be a list of strings", e); } } - } - @Override - public String name() { - return this.name; - } - - @Override - public String main() { - return this.main; - } - - @Override - public String apiVersion() { - return api; - } - - @Override - public String version() { - return this.version; - } - - @Override - public List authors() { - return this.authors; + return new GeyserExtensionDescription(name, main, apiVersion, version, authors); } } 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 f029eb797..a539dcb15 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionLoader.java @@ -25,95 +25,81 @@ package org.geysermc.geyser.extension; -import org.geysermc.api.Geyser; +import lombok.RequiredArgsConstructor; +import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionDescription; import org.geysermc.geyser.api.extension.ExtensionLoader; -import org.geysermc.geyser.api.extension.GeyserExtension; +import org.geysermc.geyser.api.extension.ExtensionLogger; +import org.geysermc.geyser.api.extension.ExtensionManager; import org.geysermc.geyser.api.extension.exception.InvalidDescriptionException; import org.geysermc.geyser.api.extension.exception.InvalidExtensionException; import org.geysermc.geyser.text.GeyserLocale; -import java.io.*; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; +import java.util.Objects; import java.util.regex.Pattern; -public class GeyserExtensionLoader implements ExtensionLoader { - private final Map classes = new HashMap<>(); +@RequiredArgsConstructor +public class GeyserExtensionLoader extends ExtensionLoader { + private static final Path EXTENSION_DIRECTORY = Paths.get("extensions"); + private static final Pattern API_VERSION_PATTERN = Pattern.compile("^[0-9]+\\.[0-9]+\\.[0-9]+$"); + + private final Map> classes = new HashMap<>(); private final Map classLoaders = new HashMap<>(); + private final Map extensionContainers = new HashMap<>(); - @Override - public GeyserExtension loadExtension(File file) throws InvalidExtensionException { - if (file == null) { - throw new InvalidExtensionException("File is null"); + public GeyserExtensionContainer loadExtension(Path path) throws InvalidExtensionException, InvalidDescriptionException { + if (path == null) { + throw new InvalidExtensionException("Path is null"); } - if (!file.exists()) { - throw new InvalidExtensionException(new FileNotFoundException(file.getPath()) + " does not exist"); + if (Files.notExists(path)) { + throw new InvalidExtensionException(new NoSuchFileException(path.toString()) + " does not exist"); } - final GeyserExtensionDescription description; - try { - description = extensionDescription(file); - } catch (InvalidDescriptionException e) { - throw new InvalidExtensionException(e); - } - - final File parentFile = file.getParentFile(); - final File dataFolder = new File(parentFile, description.name()); - if (dataFolder.exists() && !dataFolder.isDirectory()) { - throw new InvalidExtensionException("The folder " + dataFolder.getPath() + " is not a directory and is the data folder for the extension " + description.name() + "!"); + GeyserExtensionDescription description = this.extensionDescription(path); + Path parentFile = path.getParent(); + Path dataFolder = parentFile.resolve(description.name()); + if (Files.exists(dataFolder) && !Files.isDirectory(dataFolder)) { + throw new InvalidExtensionException("The folder " + dataFolder + " is not a directory and is the data folder for the extension " + description.name() + "!"); } final GeyserExtensionClassLoader loader; try { - loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), description, file); + loader = new GeyserExtensionClassLoader(this, getClass().getClassLoader(), description, path); } catch (Throwable e) { throw new InvalidExtensionException(e); } - classLoaders.put(description.name(), loader); - setup(loader.extension, description, dataFolder, file); - return loader.extension; + this.classLoaders.put(description.name(), loader); + return this.setup(loader.extension(), description, dataFolder); } - private void setup(GeyserExtension extension, GeyserExtensionDescription description, File dataFolder, File file) { + private GeyserExtensionContainer setup(Extension extension, GeyserExtensionDescription description, Path dataFolder) { GeyserExtensionLogger logger = new GeyserExtensionLogger(GeyserImpl.getInstance().getLogger(), description.name()); - extension.init(Geyser.api(), this, logger, description, dataFolder, file); + GeyserExtensionContainer container = new GeyserExtensionContainer(extension, dataFolder, description, this, logger); extension.onLoad(); + return container; } - @Override - public GeyserExtensionDescription extensionDescription(File file) throws InvalidDescriptionException { - JarFile jarFile = null; - InputStream stream = null; - - try { - jarFile = new JarFile(file); - - JarEntry descriptionEntry = jarFile.getJarEntry("extension.yml"); - if (descriptionEntry == null) { - throw new InvalidDescriptionException(new FileNotFoundException("extension.yml") + " does not exist in the jar file!"); - } - - stream = jarFile.getInputStream(descriptionEntry); - return new GeyserExtensionDescription(stream); - } catch (IOException e) { - throw new InvalidDescriptionException(e); - } finally { - if (jarFile != null) { - try { - jarFile.close(); - } catch (IOException e) { - } - } - if (stream != null) { - try { - stream.close(); - } catch (IOException e) { - } - } + public GeyserExtensionDescription extensionDescription(Path path) throws InvalidDescriptionException { + Map environment = new HashMap<>(); + try (FileSystem fileSystem = FileSystems.newFileSystem(path, environment, null)) { + Path extensionYml = fileSystem.getPath("extension.yml"); + return GeyserExtensionDescription.fromYaml(Files.newBufferedReader(extensionYml)); + } catch (IOException ex) { + throw new InvalidDescriptionException("Failed to load extension description for " + path, ex); } } @@ -121,14 +107,13 @@ public class GeyserExtensionLoader implements ExtensionLoader { return new Pattern[] { Pattern.compile("^.+\\.jar$") }; } - @Override public Class classByName(final String name) throws ClassNotFoundException{ - Class clazz = classes.get(name); + Class clazz = this.classes.get(name); try { - for(GeyserExtensionClassLoader loader : classLoaders.values()) { + for(GeyserExtensionClassLoader loader : this.classLoaders.values()) { try { clazz = loader.findClass(name,false); - } catch(NullPointerException e) { + } catch(NullPointerException ignored) { } } return clazz; @@ -138,28 +123,132 @@ public class GeyserExtensionLoader implements ExtensionLoader { } void setClass(String name, final Class clazz) { - if (!classes.containsKey(name)) { - classes.put(name,clazz); - } - } - - void removeClass(String name) { - classes.remove(name); - } - - @Override - public void enableExtension(GeyserExtension extension) { - if (!extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); - extension.setEnabled(true); + if (!this.classes.containsKey(name)) { + this.classes.put(name,clazz); } } @Override - public void disableExtension(GeyserExtension extension) { - if (extension.isEnabled()) { - GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); - extension.setEnabled(false); + protected void loadAllExtensions(@NonNull ExtensionManager extensionManager) { + // noinspection ConstantConditions + if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_dev_environment")); + return; } + + if (!GeyserImpl.VERSION.contains(".")) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); + return; + } + + String[] apiVersion = GeyserImpl.VERSION.split("\\."); + + try { + if (Files.notExists(EXTENSION_DIRECTORY)) { + Files.createDirectory(EXTENSION_DIRECTORY); + } + + Map extensions = new LinkedHashMap<>(); + Map loadedExtensions = new LinkedHashMap<>(); + + Pattern[] extensionFilters = this.extensionFilters(); + + Files.walk(EXTENSION_DIRECTORY).forEach(path -> { + if (Files.isDirectory(path)) { + return; + } + + for (Pattern filter : extensionFilters) { + if (!filter.matcher(path.getFileName().toString()).matches()) { + return; + } + } + + try { + ExtensionDescription description = this.extensionDescription(path); + if (description == null) { + return; + } + + String name = description.name(); + if (extensions.containsKey(name) || extensionManager.extension(name) != null) { + GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, path.toString())); + return; + } + + 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, apiVersion[0] + "." + apiVersion[1])); + return; + } + + String[] versionArray = description.apiVersion().split("\\."); + + // Completely different API version + if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + return; + } + + // If the extension requires new API features, being backwards compatible + if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); + return; + } + + extensions.put(name, path); + loadedExtensions.put(name, this.loadExtension(path)); + } catch (Exception e) { + GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", path.getFileName(), path.toAbsolutePath()), e); + } + }); + + for (GeyserExtensionContainer container : loadedExtensions.values()) { + this.extensionContainers.put(container.extension(), container); + this.register(container.extension(), extensionManager); + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + @Override + protected boolean isEnabled(@NonNull Extension extension) { + return this.extensionContainers.get(extension).enabled; + } + + @Override + protected void setEnabled(@NonNull Extension extension, boolean enabled) { + boolean isEnabled = this.extensionContainers.get(extension).enabled; + if (isEnabled != enabled) { + this.extensionContainers.get(extension).enabled = enabled; + if (enabled) { + extension.onEnable(); + } else { + extension.onDisable(); + } + } + } + + @NonNull + @Override + protected Path dataFolder(@NonNull Extension extension) { + return this.extensionContainers.get(extension).dataFolder(); + } + + @NonNull + @Override + protected ExtensionDescription description(@NonNull Extension extension) { + return this.extensionContainers.get(extension).description(); + } + + @NonNull + @Override + protected ExtensionLogger logger(@NonNull Extension extension) { + return this.extensionContainers.get(extension).logger(); } } diff --git a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java index 169053182..a72712f16 100644 --- a/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java +++ b/core/src/main/java/org/geysermc/geyser/extension/GeyserExtensionManager.java @@ -25,205 +25,126 @@ package org.geysermc.geyser.extension; +import net.kyori.adventure.key.Key; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.extension.ExtensionDescription; -import org.geysermc.geyser.api.extension.GeyserExtension; +import org.geysermc.geyser.api.extension.Extension; +import org.geysermc.geyser.api.extension.ExtensionManager; +import org.geysermc.geyser.api.extension.ExtensionLoader; +import org.geysermc.geyser.registry.Registries; import org.geysermc.geyser.text.GeyserLocale; -import java.io.File; -import java.lang.reflect.Constructor; -import java.util.*; -import java.util.regex.Pattern; +import org.jetbrains.annotations.NotNull; -public class GeyserExtensionManager { - protected Map extensions = new LinkedHashMap<>(); - protected Map fileAssociations = new HashMap<>(); +import java.util.*; + +public class GeyserExtensionManager extends ExtensionManager { + private static final Key BASE_KEY = Key.key("geysermc", "base"); + + private final Map extensions = new LinkedHashMap<>(); + private final Map extensionsLoaders = new LinkedHashMap<>(); public void init() { GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.loading")); - this.registerInterface(GeyserExtensionLoader.class); - this.loadExtensions(new File("extensions")); + this.registerExtensionLoader(BASE_KEY, new GeyserExtensionLoader()); + + for (ExtensionLoader loader : this.extensionLoaders().values()) { + this.loadAllExtensions(loader); + } GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.load.done", this.extensions.size())); } - public GeyserExtension getExtension(String name) { + @Override + public Extension extension(@NonNull String name) { if (this.extensions.containsKey(name)) { return this.extensions.get(name); } - return null; - } - - public Map getExtensions() { - return this.extensions; - } - - public void registerInterface(Class loader) { - GeyserExtensionLoader instance; - - if (GeyserExtensionLoader.class.isAssignableFrom(loader)) { - Constructor constructor; - - try { - constructor = loader.getConstructor(); - instance = constructor.newInstance(); - } catch (NoSuchMethodException ex) { // This should never happen - String className = loader.getName(); - - throw new IllegalArgumentException("Class " + className + " does not have a public constructor", ex); - } catch (Exception ex) { // This should never happen - throw new IllegalArgumentException("Unexpected exception " + ex.getClass().getName() + " while attempting to construct a new instance of " + loader.getName(), ex); - } - } else { - throw new IllegalArgumentException("Class " + loader.getName() + " does not implement interface ExtensionLoader"); - } - - Pattern[] patterns = instance.extensionFilters(); - - synchronized (this) { - for (Pattern pattern : patterns) { - fileAssociations.put(pattern, instance); - } - } - } - - public GeyserExtension loadExtension(File file, Map loaders) { - for (GeyserExtensionLoader loader : (loaders == null ? this.fileAssociations : loaders).values()) { - for (Pattern pattern : loader.extensionFilters()) { - if (pattern.matcher(file.getName()).matches()) { - try { - ExtensionDescription description = loader.extensionDescription(file); - if (description != null) { - GeyserExtension extension = loader.loadExtension(file); - - if (extension != null) { - this.extensions.put(extension.description().name(), extension); - - return extension; - } - } - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed"), e); - return null; - } - } - } - } return null; } - public Map loadExtensions(File dictionary) { - if (GeyserImpl.VERSION.equalsIgnoreCase("dev")) { // If your IDE says this is always true, ignore it, it isn't. - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_dev_environment")); - return new HashMap<>(); - } - if (!GeyserImpl.VERSION.contains(".")) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_version_number")); - return new HashMap<>(); - } - - String[] apiVersion = GeyserImpl.VERSION.split("\\."); - - if (!dictionary.exists()) { - dictionary.mkdir(); - } - if (!dictionary.isDirectory()) { - return new HashMap<>(); - } - - Map extensions = new LinkedHashMap<>(); - Map loadedExtensions = new LinkedHashMap<>(); - - for (final GeyserExtensionLoader loader : this.fileAssociations.values()) { - for (File file : dictionary.listFiles((dir, name) -> { - for (Pattern pattern : loader.extensionFilters()) { - if (pattern.matcher(name).matches()) { - return true; - } - } - return false; - })) { - if (file.isDirectory()) { - continue; - } - - try { - ExtensionDescription description = loader.extensionDescription(file); - if (description != null) { - String name = description.name(); - - if (extensions.containsKey(name) || this.getExtension(name) != null) { - GeyserImpl.getInstance().getLogger().warning(GeyserLocale.getLocaleStringLog("geyser.extensions.load.duplicate", name, file.getName())); - continue; - } - - try { - //Check the format: majorVersion.minorVersion.patch - if (!Pattern.matches("^[0-9]+\\.[0-9]+\\.[0-9]+$", description.apiVersion())) { - throw new IllegalArgumentException(); - } - } catch (NullPointerException | IllegalArgumentException e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_format", name, apiVersion[0] + "." + apiVersion[1])); - continue; - } - - String[] versionArray = description.apiVersion().split("\\."); - - //Completely different API version - if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); - continue; - } - - //If the extension requires new API features, being backwards compatible - if (Integer.parseInt(versionArray[1]) > Integer.parseInt(apiVersion[1])) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_api_version", name, apiVersion[0] + "." + apiVersion[1])); - continue; - } - - extensions.put(name, file); - loadedExtensions.put(name, this.loadExtension(file, this.fileAssociations)); - } - } catch (Exception e) { - GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.load.failed_with_name", file.getName(), dictionary.getAbsolutePath()), e); - } - } - } - - return loadedExtensions; - } - - public void enableExtension(GeyserExtension extension) { + @Override + public void enable(@NonNull Extension extension) { if (!extension.isEnabled()) { try { - extension.extensionLoader().enableExtension(extension); + this.enableExtension(extension); } catch (Exception e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.failed", extension.name()), e); - this.disableExtension(extension); + this.disable(extension); } } } - public void disableExtension(GeyserExtension extension) { + @Override + public void disable(@NonNull Extension extension) { if (extension.isEnabled()) { try { - extension.extensionLoader().disableExtension(extension); + this.disableExtension(extension); } catch (Exception e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.failed", extension.name()), e); } } } + public void enableExtension(Extension extension) { + if (!extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.enable.success", extension.description().name())); + extension.setEnabled(true); + } + } + + private void disableExtension(@NonNull Extension extension) { + if (extension.isEnabled()) { + GeyserImpl.getInstance().getLogger().info(GeyserLocale.getLocaleStringLog("geyser.extensions.disable.success", extension.description().name())); + extension.setEnabled(false); + } + } + public void enableExtensions() { - for (GeyserExtension extension : this.getExtensions().values()) { - this.enableExtension(extension); + for (Extension extension : this.extensions()) { + this.enable(extension); } } public void disableExtensions() { - for (GeyserExtension extension : this.getExtensions().values()) { - this.disableExtension(extension); + for (Extension extension : this.extensions()) { + this.disable(extension); } } + + @Override + public ExtensionLoader extensionLoader(@NonNull Extension extension) { + return this.extensionsLoaders.get(extension); + } + + @NonNull + @Override + public Collection extensions() { + return Collections.unmodifiableCollection(this.extensions.values()); + } + + @Nullable + @Override + public ExtensionLoader extensionLoader(@NonNull Key identifier) { + return Registries.EXTENSION_LOADERS.get(identifier); + } + + @Override + public void registerExtensionLoader(@NonNull Key identifier, @NotNull ExtensionLoader extensionLoader) { + Registries.EXTENSION_LOADERS.register(identifier, extensionLoader); + } + + @NonNull + @Override + public Map extensionLoaders() { + return Collections.unmodifiableMap(Registries.EXTENSION_LOADERS.get()); + } + + @Override + public void register(@NotNull Extension extension, @NotNull ExtensionLoader loader) { + this.extensionsLoaders.put(extension, loader); + this.extensions.put(extension.name(), extension); + } } diff --git a/core/src/main/java/org/geysermc/geyser/registry/Registries.java b/core/src/main/java/org/geysermc/geyser/registry/Registries.java index 3b0c1f138..173bfbd1a 100644 --- a/core/src/main/java/org/geysermc/geyser/registry/Registries.java +++ b/core/src/main/java/org/geysermc/geyser/registry/Registries.java @@ -31,7 +31,6 @@ import com.github.steveice10.mc.protocol.data.game.level.event.SoundEvent; import com.github.steveice10.mc.protocol.data.game.level.particle.ParticleType; import com.github.steveice10.mc.protocol.data.game.recipe.Recipe; import com.github.steveice10.mc.protocol.data.game.recipe.RecipeType; -import com.github.steveice10.mc.protocol.data.game.statistic.CustomStatistic; import com.github.steveice10.packetlib.packet.Packet; import com.nukkitx.nbt.NbtMap; import com.nukkitx.protocol.bedrock.BedrockPacket; @@ -42,6 +41,8 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.kyori.adventure.key.Key; +import org.geysermc.geyser.api.extension.ExtensionLoader; import org.geysermc.geyser.entity.EntityDefinition; import org.geysermc.geyser.registry.populator.PacketRegistryPopulator; import org.geysermc.geyser.translator.collision.BlockCollision; @@ -62,7 +63,6 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.IntFunction; /** * Holds all the common registries in Geyser. @@ -113,6 +113,11 @@ public final class Registries { */ public static final SimpleMappedRegistry> ENTITY_DEFINITIONS = SimpleMappedRegistry.create(RegistryLoaders.empty(() -> new EnumMap<>(EntityType.class))); + /** + * A map containing all the extension loaders. + */ + public static final SimpleMappedRegistry EXTENSION_LOADERS = SimpleMappedRegistry.create(RegistryLoaders.empty(Object2ObjectOpenHashMap::new)); + /** * A map containing all Java entity identifiers and their respective Geyser definitions */ diff --git a/pom.xml b/pom.xml index 004d58666..db512e477 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,8 @@ UTF-8 16 16 + + 4.9.3