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 extends GeyserConnection> 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 extends GeyserExtension> extensionClass;
+ Class extends Extension> 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 extends String>) yamlMap.get("authors"));
+ authors.addAll((Collection extends String>) 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 extends GeyserExtensionLoader> loader) {
- GeyserExtensionLoader instance;
-
- if (GeyserExtensionLoader.class.isAssignableFrom(loader)) {
- Constructor extends GeyserExtensionLoader> 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