From dc828d56d02db37f64054f94906dee23fc000a65 Mon Sep 17 00:00:00 2001 From: Ryder Belserion Date: Sun, 30 Nov 2025 09:37:51 -0500 Subject: [PATCH 1/6] Add the initial re-write of the addons module --- .../fusion/addons/AddonClassLoader.java | 5 +- .../fusion/addons/AddonManager.java | 10 +- .../fusion/addons/interfaces/IAddon.java | 127 ++---------------- .../fusion/addons/utils/FileUtils.java | 43 ++++++ .../fusion/addons/v2/ExtensionExample.java | 16 +++ .../fusion/addons/v2/ExtensionManager.java | 64 +++++++++ .../fusion/addons/v2/api/Extension.java | 43 ++++++ .../fusion/addons/v2/api/ExtensionMeta.java | 75 +++++++++++ .../addons/v2/api/interfaces/IExtension.java | 46 +++++++ .../v2/api/interfaces/IExtensionMeta.java | 18 +++ .../SimpleExtensionClassLoader.java | 78 +++++++++++ .../exceptions/InvalidExtensionException.java | 14 ++ 12 files changed, 411 insertions(+), 128 deletions(-) create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/utils/FileUtils.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtensionMeta.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/exceptions/InvalidExtensionException.java diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java index 5d454e17..ef623c71 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java @@ -60,10 +60,7 @@ public AddonClassLoader(@NotNull final AddonManager manager, @NotNull final Path } try { - this.addon = addonClass.getDeclaredConstructor().newInstance(); - this.addon.setLoader(this); - this.addon.setName(this.name = name); - this.addon.setGroup(group); + this.addon = addonClass.getDeclaredConstructor(String.class, AddonClassLoader.class, String.class).newInstance(this.name = name, this, group); } catch (final Exception exception) { throw new IllegalStateException(String.format("Failed to load main class for addon %s!", name), exception); } diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java index 0aaf441a..91ea9872 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java @@ -139,11 +139,7 @@ public Optional getAddonInstance(@NotNull final Class c * @return {@code Optional} an optional containing the addon instance */ public Optional getAddonInstance(@NotNull final String name) { - if (this.loaders.containsKey(name)) { - return Optional.ofNullable(this.loaders.get(name).getAddon()); - } - - return Optional.empty(); + return Optional.ofNullable(this.loaders.getOrDefault(name, null).getAddon()); } /** @@ -167,7 +163,7 @@ public IAddon reloadAddon(@NotNull final T addon) throws Ille final IAddon key = loader.getAddon(); if (key != null) { - key.enable(this.folder.resolve(key.getName())); + key.enable(); } foundKey = key; @@ -287,7 +283,7 @@ public void unloadAddon(@NotNull final T addon) throws Illega * @return {@link AddonManager} */ public @NotNull AddonManager enableAddons() { - this.loaders.values().stream().map(AddonClassLoader::getAddon).filter(Objects::nonNull).filter(addOn -> !addOn.isEnabled()).forEach(addon -> addon.enable(this.folder.resolve(addon.getName()))); + this.loaders.values().stream().map(AddonClassLoader::getAddon).filter(Objects::nonNull).filter(addOn -> !addOn.isEnabled()).forEach(addon -> addon.enable()); return this; } diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java index a4ef4600..7ad47abc 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java @@ -1,81 +1,38 @@ package com.ryderbelserion.fusion.addons.interfaces; import com.ryderbelserion.fusion.addons.AddonClassLoader; -import com.ryderbelserion.fusion.files.FileManager; import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; /** * The addon class, handles all things related to addons. */ public abstract class IAddon { + private final AddonClassLoader loader; + private final String name; + /** * The addon class, handles all things related to addons. */ - public IAddon() { - + public IAddon(@NotNull final String name, @NotNull final AddonClassLoader loader, @NotNull final String group) { + this.name = name; + this.loader = loader; } - private FileManager fileManager; - private AddonClassLoader loader; private boolean isEnabled; - private Logger logger; - private String name; - private Path folder; - private String group; - - /** - * Enables the addon. - */ - public void onEnable() { - setEnabled(true); - if (this.folder != null && !Files.exists(this.folder)) { - try { - Files.createDirectory(this.folder); - } catch (final IOException exception) { - throw new IllegalStateException("Cannot enable the addon, the folder %s did not get created.".formatted(this.folder)); - } - - this.fileManager = new FileManager(this.folder); - } - } - - /** - * Disables the addon. - */ - public void onDisable() { - setEnabled(false); - } - - public void onReload() { - if (this.folder != null && !Files.exists(this.folder)) { - try { - Files.createDirectory(this.folder); - } catch (final IOException exception) { - throw new IllegalStateException("Cannot enable the addon, the folder %s did not get created.".formatted(this.folder)); - } - } - } + public void onEnable() {} + public void onDisable() {} + public void onReload() {} /** * Enables an addon, this includes adding it to the class path. - * - * @param folder the addon's folder */ - public void enable(@NotNull final Path folder) { + public void enable() { if (this.isEnabled()) { throw new IllegalStateException("Cannot enable the addon when it's already enabled"); } - this.folder = folder; - this.onEnable(); } @@ -90,15 +47,6 @@ public void disable() { this.onDisable(); } - /** - * Sets the addon class loader - * - * @param loader {@link AddonClassLoader} - */ - public void setLoader(@NotNull final AddonClassLoader loader) { - this.loader = loader; - } - /** * Retrieves an instance of the addon class loader. * @@ -126,43 +74,6 @@ public boolean isEnabled() { return this.isEnabled; } - /** - * Sets the name of the addon, and creates a logger impl. - * - * @param name the name of the addon - */ - public void setName(@NotNull final String name) { - this.name = name; - } - - /** - * Gets the group i.e. the domain - * - * @param group the group - */ - public void setGroup(@NotNull final String group) { - this.logger = LoggerFactory.getLogger(group); - this.group = group; - } - - /** - * Get an instance of the FileManager. - * - * @return the file manager - */ - public @NotNull final FileManager getFileManager() { - return this.fileManager; - } - - /** - * Gets an instance of the logger. - * - * @return the logger instance of this addon - */ - public @NotNull Logger getLogger() { - return this.logger; - } - /** * Retrieves the name of the addon. * @@ -171,22 +82,4 @@ public void setGroup(@NotNull final String group) { public @NotNull String getName() { return this.name; } - - /** - * Retrieves the group path. - * - * @return the group path of the addon - */ - public @NotNull String getGroup() { - return this.group; - } - - /** - * Retrieves the folder of the addon. - * - * @return the folder of the addon - */ - public @NotNull Path getFolder() { - return this.folder; - } } \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/utils/FileUtils.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/utils/FileUtils.java new file mode 100644 index 00000000..f4838144 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/utils/FileUtils.java @@ -0,0 +1,43 @@ +package com.ryderbelserion.fusion.addons.utils; + +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class FileUtils { + + public static List getFiles(@NotNull final Path path, @NotNull final List extensions, final int depth) { + final List files = new ArrayList<>(); + + if (Files.isDirectory(path)) { // check if resolved path is a folder to loop through! + try { + Files.walkFileTree(path, new HashSet<>(), Math.max(depth, 1), new SimpleFileVisitor<>() { + @Override + public @NotNull FileVisitResult visitFile(@NotNull final Path path, @NotNull final BasicFileAttributes attributes) { + final String name = path.getFileName().toString(); + + extensions.forEach(extension -> { + if (name.endsWith(extension)) { + files.add(path); + } + }); + + return FileVisitResult.CONTINUE; + } + }); + } catch (final IOException exception) { + throw new IllegalStateException("Failed to get a list of files", exception); + } + } + + return files; + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java new file mode 100644 index 00000000..580e8b45 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java @@ -0,0 +1,16 @@ +package com.ryderbelserion.fusion.addons.v2; + +import com.ryderbelserion.fusion.addons.v2.api.Extension; + +public class ExtensionExample extends Extension { + + @Override + public void onEnable() { + + } + + @Override + public void onDisable() { + + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java new file mode 100644 index 00000000..b34dc3c1 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java @@ -0,0 +1,64 @@ +package com.ryderbelserion.fusion.addons.v2; + +import com.ryderbelserion.fusion.addons.utils.FileUtils; +import com.ryderbelserion.fusion.addons.v2.api.Extension; +import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +public class ExtensionManager { + + private final Map extensions = new ConcurrentHashMap<>(); + private final Map> classMap = new ConcurrentHashMap<>(); + + private final Path parent; // parent path i.e. the extensions folder + + public ExtensionManager(@NotNull final Path parent) { + this.parent = parent; + + init(1); + } + + public void init(final int depth) { + try { + Files.createDirectories(this.parent); + } catch (final IOException exception) { + throw new IllegalStateException("Could not create folder %s!".formatted(this.parent), exception); + } + + final List paths = FileUtils.getFiles(this.parent, List.of(".jar"), depth); + + for (final Path path : paths) { + loadExtension(path); + } + } + + public void loadExtension(@NotNull final Path path) { + final Extension extension = new Extension(); + + extension.init(this.parent, path); + + final String name = extension.getName(); + + if (this.extensions.containsKey(name)) { + throw new IllegalStateException("Cannot have 2 extensions with the same name! Extension Name: %s".formatted(name)); + } + + this.extensions.put(name, extension); + } + + public final boolean isExtensionEnabled(@NotNull final String name) { + final Optional extension = getExtension(name); + + return extension.map(Extension::isEnabled).orElse(false); + } + + public Optional getExtension(@NotNull final String name) { + return Optional.ofNullable(this.extensions.get(name)); + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java new file mode 100644 index 00000000..d929fadd --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java @@ -0,0 +1,43 @@ +package com.ryderbelserion.fusion.addons.v2.api; + +import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtension; +import com.ryderbelserion.fusion.addons.v2.entrypoint.classloaders.SimpleExtensionClassLoader; +import com.ryderbelserion.fusion.addons.v2.exceptions.InvalidExtensionException; +import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.nio.file.Path; + +public class Extension extends IExtension { + + private SimpleExtensionClassLoader classLoader; + + public Extension() {} + + @Override + public void init(@NotNull final Path parent, @NotNull final Path path) { + super.init(parent, path); + + try { + this.classLoader = new SimpleExtensionClassLoader( + path, + parent, + this, + getClass().getClassLoader() + ); + } catch (final IOException | InvalidExtensionException exception) { + throw new RuntimeException(exception); + } + } + + private boolean isEnabled = false; + + @Override + public void setEnabled(final boolean isEnabled) { + this.isEnabled = isEnabled; + } + + @Override + public final boolean isEnabled() { + return this.isEnabled; + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java new file mode 100644 index 00000000..b8642d97 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java @@ -0,0 +1,75 @@ +package com.ryderbelserion.fusion.addons.v2.api; + +import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtensionMeta; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; + +public abstract class ExtensionMeta implements IExtensionMeta { + + private String version; + private Logger logger; + private String main; + private String name; + + private Path path; + + public void init(@NotNull final Path parent, @NotNull final Path path) { + final Properties properties = new Properties(); + + try (final FileSystem entry = FileSystems.newFileSystem(path, (ClassLoader) null); final InputStream stream = Files.newInputStream(entry.getPath("addon.properties"))) { + properties.load(stream); + } catch (final IOException exception) { + throw new IllegalStateException("Failed to load addon.properties!", exception); + } + + final String pathName = path.getFileName().toString(); + + this.version = properties.getProperty("version", "N/A"); + this.main = properties.getProperty("main", "N/A"); + this.name = properties.getProperty("name", pathName); + + if (this.main.isEmpty() || this.main.equals("N/A")) { + throw new IllegalStateException("Extension group cannot be empty for %s.".formatted(pathName)); + } + + if (this.name.isEmpty()) { + throw new IllegalStateException("Extension name cannot be empty for %s.".formatted(pathName)); + } + + this.logger = LoggerFactory.getLogger(this.name); + this.path = path; + } + + @Override + public Path getDataDirectory() { + return this.path; + } + + @Override + public String getMainClass() { + return this.main; + } + + @Override + public String getVersion() { + return this.version; + } + + @Override + public Logger getLogger() { + return this.logger; + } + + @Override + public String getName() { + return this.name; + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java new file mode 100644 index 00000000..bdc021ad --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java @@ -0,0 +1,46 @@ +package com.ryderbelserion.fusion.addons.v2.api.interfaces; + +import com.ryderbelserion.fusion.addons.v2.api.ExtensionMeta; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public abstract class IExtension extends ExtensionMeta { + + public IExtension() {} + + public void onEnable() { + final Path path = getDataDirectory(); + + if (!Files.exists(path)) { + try { + Files.createDirectory(path); + } catch (final IOException exception) { + throw new IllegalStateException("Cannot enable the extension, the folder %s did not get created.".formatted(path)); + } + } + + setEnabled(true); + } + + public void onDisable() { + setEnabled(false); + } + + public void onReload() { + final Path path = getDataDirectory(); + + if (!Files.exists(path)) { + try { + Files.createDirectory(path); + } catch (final IOException exception) { + throw new IllegalStateException("Cannot enable the extension, the folder %s did not get created.".formatted(path)); + } + } + } + + public abstract void setEnabled(final boolean isEnabled); + + public abstract boolean isEnabled(); + +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtensionMeta.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtensionMeta.java new file mode 100644 index 00000000..c3e32f98 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtensionMeta.java @@ -0,0 +1,18 @@ +package com.ryderbelserion.fusion.addons.v2.api.interfaces; + +import org.slf4j.Logger; +import java.nio.file.Path; + +public interface IExtensionMeta { + + Path getDataDirectory(); + + String getMainClass(); + + String getVersion(); + + Logger getLogger(); + + String getName(); + +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java new file mode 100644 index 00000000..d8bb7dab --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java @@ -0,0 +1,78 @@ +package com.ryderbelserion.fusion.addons.v2.entrypoint.classloaders; + +import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtension; +import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtensionMeta; +import com.ryderbelserion.fusion.addons.v2.exceptions.InvalidExtensionException; +import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarFile; + +public class SimpleExtensionClassLoader extends URLClassLoader { + + private final Map> classes = new ConcurrentHashMap<>(); + + protected final IExtensionMeta config; + protected final IExtension extension; + protected final JarFile jarFile; + protected final Path source; + protected final Path path; + protected final URL url; + + public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path source, + @NotNull final IExtensionMeta config, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { + super(path.getFileName().toString(), new URL[]{path.toUri().toURL()}, loader); + + this.jarFile = new JarFile(source.toFile()); + this.source = source; + this.config = config; + this.path = path; + + this.url = this.path.toUri().toURL(); + + Class mainClass; + + try { + mainClass = Class.forName(this.config.getMainClass(), true, this); + + this.classes.put(mainClass.getName(), mainClass); + } catch (final ClassNotFoundException exception) { + throw new InvalidExtensionException("Could not find main class %s,".formatted(this.config.getMainClass()), exception); + } + + Class extension; + + try { + extension = mainClass.asSubclass(IExtension.class); + } catch (final Exception exception) { + throw new InvalidExtensionException("Main Class %s must extend Extension!".formatted(this.config.getMainClass()), exception); + } + + try { + this.extension = extension.getDeclaredConstructor().newInstance(); + } catch (final IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException exception) { + throw new InvalidExtensionException("Failed to load main class for the extension %s!".formatted(this.config.getName()), exception); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + Class result = this.classes.get(name); + + if (result == null) { + this.classes.put(name, result = super.findClass(name)); + } + + return result; + } + + public @NotNull Collection> getClasses() { + return this.classes.values(); + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/exceptions/InvalidExtensionException.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/exceptions/InvalidExtensionException.java new file mode 100644 index 00000000..8666b4f4 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/exceptions/InvalidExtensionException.java @@ -0,0 +1,14 @@ +package com.ryderbelserion.fusion.addons.v2.exceptions; + +import org.jetbrains.annotations.NotNull; + +public class InvalidExtensionException extends Exception { + + public InvalidExtensionException(@NotNull final String message, @NotNull final Throwable cause) { + super(message, cause); + } + + public InvalidExtensionException(@NotNull final String message) { + super(message); + } +} \ No newline at end of file From ae369f12a64465350efd5dabd58b47f21a635929 Mon Sep 17 00:00:00 2001 From: Ryder Belserion Date: Tue, 2 Dec 2025 19:34:11 -0500 Subject: [PATCH 2/6] Fix startup issues --- .../com/ryderbelserion/fusion/addons/v2/ExtensionManager.java | 2 -- .../v2/entrypoint/classloaders/SimpleExtensionClassLoader.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java index b34dc3c1..64928c05 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java @@ -20,8 +20,6 @@ public class ExtensionManager { public ExtensionManager(@NotNull final Path parent) { this.parent = parent; - - init(1); } public void init(final int depth) { diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java index d8bb7dab..86f7375b 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java @@ -29,7 +29,7 @@ public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path @NotNull final IExtensionMeta config, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { super(path.getFileName().toString(), new URL[]{path.toUri().toURL()}, loader); - this.jarFile = new JarFile(source.toFile()); + this.jarFile = new JarFile(path.toFile()); this.source = source; this.config = config; this.path = path; From 449eb72c8d7af604c85127b2403d331bce5b5e48 Mon Sep 17 00:00:00 2001 From: Ryder Belserion Date: Tue, 2 Dec 2025 21:57:36 -0500 Subject: [PATCH 3/6] Fix more issues on startup --- .../fusion/addons/v2/ExtensionManager.java | 7 +++++++ .../fusion/addons/v2/api/ExtensionMeta.java | 9 ++++++++- examples/extension/build.gradle.kts | 8 ++++++++ .../java/me/corecraft/currency/Currency.java | 20 +++++++++++++++++++ .../src/main/resources/addon.properties | 4 ++++ plugin/build.gradle.kts | 1 + .../com/ryderbelserion/fusion/Fusion.java | 5 +++++ settings.gradle.kts | 4 +++- 8 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 examples/extension/build.gradle.kts create mode 100644 examples/extension/src/main/java/me/corecraft/currency/Currency.java create mode 100644 examples/extension/src/main/resources/addon.properties diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java index 64928c05..cb4721da 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java @@ -3,6 +3,9 @@ import com.ryderbelserion.fusion.addons.utils.FileUtils; import com.ryderbelserion.fusion.addons.v2.api.Extension; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -13,6 +16,8 @@ public class ExtensionManager { + private final Logger logger = LoggerFactory.getLogger(ExtensionManager.class); + private final Map extensions = new ConcurrentHashMap<>(); private final Map> classMap = new ConcurrentHashMap<>(); @@ -37,6 +42,8 @@ public void init(final int depth) { } public void loadExtension(@NotNull final Path path) { + this.logger.info("Loading the extension {}.", path.getFileName()); + final Extension extension = new Extension(); extension.init(this.parent, path); diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java index b8642d97..cbc4d94d 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java @@ -45,7 +45,14 @@ public void init(@NotNull final Path parent, @NotNull final Path path) { } this.logger = LoggerFactory.getLogger(this.name); - this.path = path; + + this.path = parent.resolve(this.name); + + try { + Files.createDirectory(this.path); + } catch (final IOException exception) { + this.logger.warn("Failed to create directory {}", this.path); + } } @Override diff --git a/examples/extension/build.gradle.kts b/examples/extension/build.gradle.kts new file mode 100644 index 00000000..d5a5077c --- /dev/null +++ b/examples/extension/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + `config-java` +} + +dependencies { + compileOnly("ch.qos.logback:logback-classic:1.5.20") + compileOnly(project(":fusion-addons")) +} \ No newline at end of file diff --git a/examples/extension/src/main/java/me/corecraft/currency/Currency.java b/examples/extension/src/main/java/me/corecraft/currency/Currency.java new file mode 100644 index 00000000..0eec3ba9 --- /dev/null +++ b/examples/extension/src/main/java/me/corecraft/currency/Currency.java @@ -0,0 +1,20 @@ +package me.corecraft.currency; + +import com.ryderbelserion.fusion.addons.v2.api.Extension; + +public class Currency extends Extension { + + @Override + public void onEnable() { + super.onEnable(); + + getLogger().warn("The extension is enabled!"); + } + + @Override + public void onDisable() { + super.onDisable(); + + getLogger().warn("The extension is disabled!"); + } +} \ No newline at end of file diff --git a/examples/extension/src/main/resources/addon.properties b/examples/extension/src/main/resources/addon.properties new file mode 100644 index 00000000..ef892127 --- /dev/null +++ b/examples/extension/src/main/resources/addon.properties @@ -0,0 +1,4 @@ +name=Currency +main=me.corecraft.currency.Currency +version=0.1.0 +description=A currency module \ No newline at end of file diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 0c519554..d682137a 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -5,6 +5,7 @@ plugins { project.group = "${rootProject.group}.paper" dependencies { + api(project(":fusion-addons")) api(project(":fusion-paper")) } diff --git a/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java b/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java index 062f7f15..e240c8df 100644 --- a/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java +++ b/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java @@ -1,5 +1,6 @@ package com.ryderbelserion.fusion; +import com.ryderbelserion.fusion.addons.v2.ExtensionManager; import com.ryderbelserion.fusion.paper.FusionPaper; import com.ryderbelserion.fusion.paper.builders.ItemBuilder; import org.bukkit.entity.Player; @@ -21,6 +22,10 @@ public Fusion(@NotNull final FusionPaper fusion) { @Override public void onEnable() { + final ExtensionManager manager = new ExtensionManager(getDataPath().resolve("extensions")); + + manager.init(1); + this.fusion.setPlugin(this).init(); getServer().getPluginManager().registerEvents(this, this); diff --git a/settings.gradle.kts b/settings.gradle.kts index fb30f3b5..b00f5d0b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -24,7 +24,9 @@ listOf( listOf( "minecraft/kyori" to "kyori", - "minecraft/paper" to "paper" + "minecraft/paper" to "paper", + + "examples/extension" to "extension" ).forEach { includeProject(it.first, it.second) } From 5fe25d345e4f0b90e5639dbbea60dad848283d29 Mon Sep 17 00:00:00 2001 From: Ryder Belserion Date: Wed, 3 Dec 2025 00:35:30 -0500 Subject: [PATCH 4/6] Class loaders confuse the fuck out of me lol --- addons/build.gradle.kts | 3 +-- .../fusion/addons/v2/ExtensionExample.java | 16 ------------- .../fusion/addons/v2/ExtensionManager.java | 7 +++--- .../fusion/addons/v2/api/ExtensionMeta.java | 12 ++++++---- .../SimpleExtensionClassLoader.java | 23 ++++++------------- examples/extension/build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 +++ .../ryderbelserion/fusion/FusionLoader.java | 6 ++--- 8 files changed, 26 insertions(+), 46 deletions(-) delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java diff --git a/addons/build.gradle.kts b/addons/build.gradle.kts index cfd0e0f0..de96fe50 100644 --- a/addons/build.gradle.kts +++ b/addons/build.gradle.kts @@ -7,7 +7,6 @@ project.group = "${rootProject.name}.addons" project.version = "0.5.0" dependencies { - compileOnly("ch.qos.logback:logback-classic:1.5.20") - api(project(":fusion-files")) + compileOnly(libs.log4j) } \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java deleted file mode 100644 index 580e8b45..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionExample.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ryderbelserion.fusion.addons.v2; - -import com.ryderbelserion.fusion.addons.v2.api.Extension; - -public class ExtensionExample extends Extension { - - @Override - public void onEnable() { - - } - - @Override - public void onDisable() { - - } -} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java index cb4721da..24456299 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java @@ -5,7 +5,6 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -36,14 +35,16 @@ public void init(final int depth) { final List paths = FileUtils.getFiles(this.parent, List.of(".jar"), depth); + this.logger.warn("Initializing extensions..."); + for (final Path path : paths) { loadExtension(path); } + + this.logger.warn("Initialized {} extension(s)", this.extensions.size()); } public void loadExtension(@NotNull final Path path) { - this.logger.info("Loading the extension {}.", path.getFileName()); - final Extension extension = new Extension(); extension.init(this.parent, path); diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java index cbc4d94d..0bb4960d 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java @@ -46,12 +46,16 @@ public void init(@NotNull final Path parent, @NotNull final Path path) { this.logger = LoggerFactory.getLogger(this.name); + this.logger.warn("Loading the extension {}.", this.name); + this.path = parent.resolve(this.name); - try { - Files.createDirectory(this.path); - } catch (final IOException exception) { - this.logger.warn("Failed to create directory {}", this.path); + if (!Files.exists(this.path)) { + try { + Files.createDirectory(this.path); + } catch (final IOException ignored) { + this.logger.warn("Failed to create directory {}", this.path); + } } } diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java index 86f7375b..173a23a4 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java @@ -1,11 +1,9 @@ package com.ryderbelserion.fusion.addons.v2.entrypoint.classloaders; import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtension; -import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtensionMeta; import com.ryderbelserion.fusion.addons.v2.exceptions.InvalidExtensionException; import org.jetbrains.annotations.NotNull; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; @@ -18,7 +16,6 @@ public class SimpleExtensionClassLoader extends URLClassLoader { private final Map> classes = new ConcurrentHashMap<>(); - protected final IExtensionMeta config; protected final IExtension extension; protected final JarFile jarFile; protected final Path source; @@ -26,12 +23,11 @@ public class SimpleExtensionClassLoader extends URLClassLoader { protected final URL url; public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path source, - @NotNull final IExtensionMeta config, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { + @NotNull final IExtension extension, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { super(path.getFileName().toString(), new URL[]{path.toUri().toURL()}, loader); this.jarFile = new JarFile(path.toFile()); this.source = source; - this.config = config; this.path = path; this.url = this.path.toUri().toURL(); @@ -39,26 +35,21 @@ public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path Class mainClass; try { - mainClass = Class.forName(this.config.getMainClass(), true, this); + mainClass = Class.forName(extension.getMainClass(), true, this); this.classes.put(mainClass.getName(), mainClass); } catch (final ClassNotFoundException exception) { - throw new InvalidExtensionException("Could not find main class %s,".formatted(this.config.getMainClass()), exception); + throw new InvalidExtensionException("Could not find main class %s,".formatted(extension.getMainClass()), exception); } - Class extension; - try { - extension = mainClass.asSubclass(IExtension.class); + mainClass.asSubclass(IExtension.class); } catch (final Exception exception) { - throw new InvalidExtensionException("Main Class %s must extend Extension!".formatted(this.config.getMainClass()), exception); + throw new InvalidExtensionException("Main Class %s must extend Extension!".formatted(extension.getMainClass()), exception); } - try { - this.extension = extension.getDeclaredConstructor().newInstance(); - } catch (final IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException exception) { - throw new InvalidExtensionException("Failed to load main class for the extension %s!".formatted(this.config.getName()), exception); - } + this.extension = extension; + this.extension.onEnable(); } @Override diff --git a/examples/extension/build.gradle.kts b/examples/extension/build.gradle.kts index d5a5077c..a0ffa73e 100644 --- a/examples/extension/build.gradle.kts +++ b/examples/extension/build.gradle.kts @@ -3,6 +3,6 @@ plugins { } dependencies { - compileOnly("ch.qos.logback:logback-classic:1.5.20") compileOnly(project(":fusion-addons")) + compileOnly(libs.log4j) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6cf62bf7..c39cbdeb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ nexo = "1.6.0" # https://github.com/Nexo-MC fix-javadoc = "1.19" # https://github.com/mfnalex/gradle-fix-javadoc-plugin run-paper = "3.0.2" # https://github.com/jpenilla/run-task shadow = "9.2.2" # https://github.com/GradleUp/shadow +log4j = "2.12.4" # https://logging.apache.org/log4j/2.12.x/maven-artifacts.html [plugins] # https://github.com/mfnalex/gradle-fix-javadoc-plugin @@ -35,6 +36,8 @@ configurate-gson = { group = "org.spongepowered", name = "configurate-gson", ver velocity = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity" } # https://github.com/GradleUp/shadow shadow = { module = "com.gradleup.shadow:shadow-gradle-plugin", version.ref = "shadow" } +# https://logging.apache.org/log4j/2.12.x/maven-artifacts.html +log4j = { module = "org.apache.logging.log4j:log4j-slf4j-impl", version.ref = "log4j" } # https://github.com/JetBrains/java-annotations annotations = { module = "org.jetbrains:annotations", version.ref = "annotations" } # https://github.com/mojang/brigadier diff --git a/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java b/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java index 642d2afe..b5d5e7e9 100644 --- a/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java +++ b/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java @@ -1,13 +1,11 @@ package com.ryderbelserion.fusion; -import com.ryderbelserion.fusion.core.utils.StringUtils; import com.ryderbelserion.fusion.paper.FusionPaper; import io.papermc.paper.plugin.bootstrap.BootstrapContext; import io.papermc.paper.plugin.bootstrap.PluginBootstrap; import io.papermc.paper.plugin.bootstrap.PluginProviderContext; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; -import java.util.List; public class FusionLoader implements PluginBootstrap { @@ -17,12 +15,12 @@ public class FusionLoader implements PluginBootstrap { public void bootstrap(@NotNull BootstrapContext context) { this.fusion = new FusionPaper(context); - this.fusion.log("info", "We are starting the server!"); + /*this.fusion.log("info", "We are starting the server!"); this.fusion.log("info", StringUtils.toString(List.of( "beans", "alpha" - ))); + )));*/ } @Override From e3967dda2fbd1e098df0a37d594778f6edf3807a Mon Sep 17 00:00:00 2001 From: Ryder Belserion Date: Thu, 4 Dec 2025 00:24:39 -0500 Subject: [PATCH 5/6] Fix more stuff --- .../fusion/addons/v2/ExtensionManager.java | 4 ++- .../fusion/addons/v2/api/Extension.java | 22 +++++++------- .../fusion/addons/v2/api/ExtensionMeta.java | 3 +- .../addons/v2/api/interfaces/IExtension.java | 29 ++++--------------- .../SimpleExtensionClassLoader.java | 24 ++++++++++----- .../java/me/corecraft/currency/Currency.java | 28 +++++++++++++++--- 6 files changed, 63 insertions(+), 47 deletions(-) diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java index 24456299..57f86b2c 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java @@ -47,7 +47,7 @@ public void init(final int depth) { public void loadExtension(@NotNull final Path path) { final Extension extension = new Extension(); - extension.init(this.parent, path); + extension.init(this, this.parent, path); final String name = extension.getName(); @@ -56,6 +56,8 @@ public void loadExtension(@NotNull final Path path) { } this.extensions.put(name, extension); + + extension.onEnable(); } public final boolean isExtensionEnabled(@NotNull final String name) { diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java index d929fadd..14bd4b98 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java @@ -1,5 +1,6 @@ package com.ryderbelserion.fusion.addons.v2.api; +import com.ryderbelserion.fusion.addons.v2.ExtensionManager; import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtension; import com.ryderbelserion.fusion.addons.v2.entrypoint.classloaders.SimpleExtensionClassLoader; import com.ryderbelserion.fusion.addons.v2.exceptions.InvalidExtensionException; @@ -14,19 +15,20 @@ public class Extension extends IExtension { public Extension() {} @Override - public void init(@NotNull final Path parent, @NotNull final Path path) { - super.init(parent, path); - - try { - this.classLoader = new SimpleExtensionClassLoader( - path, - parent, - this, - getClass().getClassLoader() - ); + public void init(@NotNull final ExtensionManager manager, @NotNull final Path parent, @NotNull final Path path) { + super.init(manager, parent, path); + + try (final SimpleExtensionClassLoader classLoader = new SimpleExtensionClassLoader(path, parent, this, getClass().getClassLoader())) { + this.classLoader = classLoader; } catch (final IOException | InvalidExtensionException exception) { throw new RuntimeException(exception); } + + setEnabled(true); + } + + public @NotNull final SimpleExtensionClassLoader getClassLoader() { + return this.classLoader; } private boolean isEnabled = false; diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java index 0bb4960d..8424cf14 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java @@ -1,5 +1,6 @@ package com.ryderbelserion.fusion.addons.v2.api; +import com.ryderbelserion.fusion.addons.v2.ExtensionManager; import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtensionMeta; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -21,7 +22,7 @@ public abstract class ExtensionMeta implements IExtensionMeta { private Path path; - public void init(@NotNull final Path parent, @NotNull final Path path) { + public void init(@NotNull final ExtensionManager manager, @NotNull final Path parent, @NotNull final Path path) { final Properties properties = new Properties(); try (final FileSystem entry = FileSystems.newFileSystem(path, (ClassLoader) null); final InputStream stream = Files.newInputStream(entry.getPath("addon.properties"))) { diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java index bdc021ad..3f70386c 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java @@ -1,42 +1,23 @@ package com.ryderbelserion.fusion.addons.v2.api.interfaces; import com.ryderbelserion.fusion.addons.v2.api.ExtensionMeta; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; public abstract class IExtension extends ExtensionMeta { - public IExtension() {} + public IExtension() { - public void onEnable() { - final Path path = getDataDirectory(); + } - if (!Files.exists(path)) { - try { - Files.createDirectory(path); - } catch (final IOException exception) { - throw new IllegalStateException("Cannot enable the extension, the folder %s did not get created.".formatted(path)); - } - } + public void onEnable() { - setEnabled(true); } public void onDisable() { - setEnabled(false); + } public void onReload() { - final Path path = getDataDirectory(); - - if (!Files.exists(path)) { - try { - Files.createDirectory(path); - } catch (final IOException exception) { - throw new IllegalStateException("Cannot enable the extension, the folder %s did not get created.".formatted(path)); - } - } + } public abstract void setEnabled(final boolean isEnabled); diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java index 173a23a4..bc3cff70 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java @@ -1,9 +1,11 @@ package com.ryderbelserion.fusion.addons.v2.entrypoint.classloaders; import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtension; +import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtensionMeta; import com.ryderbelserion.fusion.addons.v2.exceptions.InvalidExtensionException; import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Path; @@ -16,6 +18,7 @@ public class SimpleExtensionClassLoader extends URLClassLoader { private final Map> classes = new ConcurrentHashMap<>(); + protected final IExtensionMeta config; protected final IExtension extension; protected final JarFile jarFile; protected final Path source; @@ -23,11 +26,12 @@ public class SimpleExtensionClassLoader extends URLClassLoader { protected final URL url; public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path source, - @NotNull final IExtension extension, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { + @NotNull final IExtensionMeta config, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { super(path.getFileName().toString(), new URL[]{path.toUri().toURL()}, loader); this.jarFile = new JarFile(path.toFile()); this.source = source; + this.config = config; this.path = path; this.url = this.path.toUri().toURL(); @@ -35,21 +39,27 @@ public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path Class mainClass; try { - mainClass = Class.forName(extension.getMainClass(), true, this); + mainClass = Class.forName(this.config.getMainClass(), true, this); this.classes.put(mainClass.getName(), mainClass); } catch (final ClassNotFoundException exception) { - throw new InvalidExtensionException("Could not find main class %s,".formatted(extension.getMainClass()), exception); + throw new InvalidExtensionException("Could not find main class %s,".formatted(this.config.getMainClass()), exception); } + Class extension; + try { - mainClass.asSubclass(IExtension.class); + extension = mainClass.asSubclass(IExtension.class); } catch (final Exception exception) { - throw new InvalidExtensionException("Main Class %s must extend Extension!".formatted(extension.getMainClass()), exception); + throw new InvalidExtensionException("Main Class %s must extend Extension!".formatted(this.config.getMainClass()), exception); } - this.extension = extension; - this.extension.onEnable(); + try { + this.extension = extension.getDeclaredConstructor().newInstance(); + } catch (final IllegalAccessException | NoSuchMethodException | InstantiationException | + InvocationTargetException exception) { + throw new InvalidExtensionException("Failed to load main class for the extension %s!".formatted(this.config.getName()), exception); + } } @Override diff --git a/examples/extension/src/main/java/me/corecraft/currency/Currency.java b/examples/extension/src/main/java/me/corecraft/currency/Currency.java index 0eec3ba9..911e82cc 100644 --- a/examples/extension/src/main/java/me/corecraft/currency/Currency.java +++ b/examples/extension/src/main/java/me/corecraft/currency/Currency.java @@ -1,20 +1,40 @@ package me.corecraft.currency; import com.ryderbelserion.fusion.addons.v2.api.Extension; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; public class Currency extends Extension { @Override public void onEnable() { - super.onEnable(); + final Path path = getDataDirectory(); - getLogger().warn("The extension is enabled!"); + if (!Files.exists(path)) { + try { + Files.createDirectory(path); + } catch (final IOException exception) { + throw new IllegalStateException("Cannot enable the extension, the folder %s did not get created.".formatted(path)); + } + } } @Override public void onDisable() { - super.onDisable(); - getLogger().warn("The extension is disabled!"); + } + + @Override + public void onReload() { + final Path path = getDataDirectory(); + + if (!Files.exists(path)) { + try { + Files.createDirectory(path); + } catch (final IOException exception) { + throw new IllegalStateException("Cannot enable the extension, the folder %s did not get created.".formatted(path)); + } + } } } \ No newline at end of file From d88abd7f78d8acc8fac05bf56186805577d8d3dc Mon Sep 17 00:00:00 2001 From: Ryder Belserion Date: Sun, 7 Dec 2025 20:57:07 -0500 Subject: [PATCH 6/6] Add latest changes to the extension framework --- .../fusion/addons/AddonClassLoader.java | 188 --------- .../fusion/addons/AddonManager.java | 374 ------------------ .../addons/{v2 => }/ExtensionManager.java | 29 +- .../fusion/addons/api/Extension.java | 53 +++ .../addons/{v2 => }/api/ExtensionMeta.java | 7 +- .../addons/api/interfaces/IExtension.java | 19 + .../api/interfaces/IExtensionManager.java | 22 ++ .../api/interfaces/IExtensionMeta.java | 2 +- .../SimpleExtensionClassLoader.java | 89 +++++ .../exceptions/InvalidExtensionException.java | 2 +- .../fusion/addons/interfaces/IAddon.java | 85 ---- .../fusion/addons/objects/Addon.java | 41 -- .../fusion/addons/v2/api/Extension.java | 45 --- .../addons/v2/api/interfaces/IExtension.java | 27 -- .../SimpleExtensionClassLoader.java | 79 ---- .../java/me/corecraft/currency/Currency.java | 6 +- .../com/ryderbelserion/fusion/Fusion.java | 14 +- .../ryderbelserion/fusion/FusionLoader.java | 2 +- plugin/src/main/resources/paper-plugin.yml | 2 +- 19 files changed, 224 insertions(+), 862 deletions(-) delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java rename addons/src/main/java/com/ryderbelserion/fusion/addons/{v2 => }/ExtensionManager.java (74%) create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/api/Extension.java rename addons/src/main/java/com/ryderbelserion/fusion/addons/{v2 => }/api/ExtensionMeta.java (88%) create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtension.java create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtensionManager.java rename addons/src/main/java/com/ryderbelserion/fusion/addons/{v2 => }/api/interfaces/IExtensionMeta.java (78%) create mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/entrypoint/classloaders/SimpleExtensionClassLoader.java rename addons/src/main/java/com/ryderbelserion/fusion/addons/{v2 => }/exceptions/InvalidExtensionException.java (86%) delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/objects/Addon.java delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java delete mode 100644 addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java deleted file mode 100644 index ef623c71..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonClassLoader.java +++ /dev/null @@ -1,188 +0,0 @@ -package com.ryderbelserion.fusion.addons; - -import com.ryderbelserion.fusion.addons.interfaces.IAddon; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Handles class loaders related to addons. - */ -public class AddonClassLoader extends URLClassLoader { - - private final Map> classes = new ConcurrentHashMap<>(); - - private final AddonManager manager; - private final IAddon addon; - private final Path path; - - private boolean isDisabling; - private String name; - - /** - * Constructs an instance of the addon class loader - * - * @param manager the addon manager - * @param path the path for the addon's directory - * @param group the entry point - * @param name the name of the addon - * @throws IllegalStateException throws {@link IllegalStateException} - * @throws MalformedURLException throws {@link MalformedURLException} - */ - public AddonClassLoader(@NotNull final AddonManager manager, @NotNull final Path path, @NotNull final String group, @NotNull final String name) throws IllegalStateException, MalformedURLException { - super(new URL[]{path.toUri().toURL()}, manager.getClass().getClassLoader()); - - this.manager = manager; - this.name = name.isBlank() ? path.getFileName().toString() : name; - this.path = path; - - Class mainClass; - - try { - mainClass = Class.forName(group, true, this); - - this.classes.put(mainClass.getName(), mainClass); - } catch (final ClassNotFoundException exception) { - throw new IllegalStateException(String.format("Could not find main class for addon %s!", name), exception); - } - - Class addonClass; - - try { - addonClass = mainClass.asSubclass(IAddon.class); - } catch (final Exception exception) { - throw new IllegalStateException(String.format("%s does not implement iAddon!", group), exception); - } - - try { - this.addon = addonClass.getDeclaredConstructor(String.class, AddonClassLoader.class, String.class).newInstance(this.name = name, this, group); - } catch (final Exception exception) { - throw new IllegalStateException(String.format("Failed to load main class for addon %s!", name), exception); - } - } - - /** - * Finds and loads the class with the specified name from the URL search - * path. Any URLs referring to JAR files are loaded and opened as needed - * until the class is found. - * - * @param name the name of the class - * @return the resulting class - * @throws ClassNotFoundException if the class could not be found, - * or if the loader is closed. - * @throws NullPointerException if {@code name} is {@code null}. - */ - @Override - protected Class findClass(@NotNull final String name) throws ClassNotFoundException { - return findClass(name, true); - } - - /** - * Finds and loads the class with the specified name from the URL search - * path. Any URLs referring to JAR files are loaded and opened as needed - * until the class is found. - * - * @param name the name of the class - * @param isGlobal true or false - * @return the resulting class - * @throws ClassNotFoundException if the class could not be found, - * or if the loader is closed. - * @throws NullPointerException if {@code name} is {@code null}. - */ - public Class findClass(@NotNull final String name, final boolean isGlobal) throws ClassNotFoundException { - if (this.isDisabling()) { - throw new ClassNotFoundException("This class loader is disabling!"); - } - - if (this.classes.containsKey(name)) { - return this.classes.get(name); - } - - Class classObject = null; - - if (isGlobal) { - classObject = this.manager.findClass(name); - } - - if (classObject == null) { - classObject = super.findClass(name); - - if (classObject != null) { - this.manager.setClass(name, classObject); - } - } - - return classObject; - } - - /** - * Removes all classes from the class path. - */ - public void removeClasses() { - if (!this.isDisabling()) { - throw new IllegalStateException("Cannot remove class when the loader isn't disabling!"); - } - - this.manager.removeAll(this.classes); - this.classes.clear(); - } - - /** - * Sets the disable status of the addon. - * - * @param isDisabling true or false - */ - public void setDisabling(final boolean isDisabling) { - this.isDisabling = isDisabling; - } - - /** - * Checks if the addon is disabling. - * - * @return true or false - */ - public boolean isDisabling() { - return this.isDisabling; - } - - /** - * Fetches an instance of {@link AddonManager}. - * - * @return the instance of {@link AddonManager} - */ - public @NotNull AddonManager getManager() { - return this.manager; - } - - /** - * Fetches an instance of {@link IAddon}. - * - * @return the instance of {@link IAddon} - */ - public @Nullable IAddon getAddon() { - return this.addon; - } - - /** - * The name of the class or addon. - * - * @return the name of the class or addon - */ - public @NotNull String getName() { - return this.name; - } - - /** - * The path of the class or addon. - * - * @return the path of the class or addon - */ - public @NotNull Path getPath() { - return this.path; - } -} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java deleted file mode 100644 index 91ea9872..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/AddonManager.java +++ /dev/null @@ -1,374 +0,0 @@ -package com.ryderbelserion.fusion.addons; - -import com.ryderbelserion.fusion.addons.interfaces.IAddon; -import com.ryderbelserion.fusion.addons.objects.Addon; -import org.jetbrains.annotations.NotNull; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Manages addons, including loading it into the class path, and caching it all. - */ -public class AddonManager { - - private final Map> classMap = new ConcurrentHashMap<>(); - private final Map loaders = new ConcurrentHashMap<>(); - - private final Path folder; - private final Path path; - - /** - * The default constructor to create an instance of {@link AddonManager} - * - * @param path the root path - */ - public AddonManager(@NotNull final Path path) { - this.folder = path.resolve("addons"); - - this.path = path; - } - - /** - * Purges all classes found in the map passed through. - * - * @param classes the map of classes - */ - public void removeAll(@NotNull final Map> classes) { - classes.keySet().forEach(this.classMap::remove); - - for (final Class object : classes.values()) { - if (this.classMap.containsValue(object)) { - throw new IllegalStateException(String.format("Class %s did not get removed.", object.getName())); - } - } - } - - /** - * Adds a class path to the concurrent cash. - * - * @param name the name of the class - * @param classObject the class object - */ - public void setClass(@NotNull final String name, @NotNull final Class classObject) { - this.classMap.put(name, classObject); - } - - /** - * Retrieves a class object by name. - * - * @param name the name of the class - * @return {@link Class} - */ - public Class findClass(@NotNull final String name) { - Class classObject = null; - - if (this.classMap.containsKey(name)) { - classObject = this.classMap.get(name); - } - - if (classObject == null) { - for (final AddonClassLoader loader : this.loaders.values()) { - try { - if ((classObject = loader.findClass(name, false)) != null) { - this.classMap.put(name, classObject); - - break; - } - } catch (final Exception ignored) {} - } - } - - return classObject; - } - - /** - * Creates the addons folder, loops through files, and loads all addons into the class path. - * - * @param depth defines how far we should check for jar/zip files - * @return {@link AddonManager} - * @throws IllegalStateException throws an exception if the directory cannot be created - */ - public @NotNull AddonManager load(final int depth) throws IllegalStateException { - try { - Files.createDirectories(this.folder); - } catch (final IOException exception) { - throw new IllegalStateException(String.format("Could not create folder %s!", this.folder), exception); - } - - final List addons = getFiles(this.folder, List.of( - ".jar", - ".zip" - ), depth); - - addons.forEach(this::loadAddon); - - return this; - } - - /** - * Retrieves an instance of the addon by using the class object - * - * @param classObject the instance - * @return {@code Optional} - * @param the extended class - */ - public Optional getAddonInstance(@NotNull final Class classObject) { - return this.loaders.values().stream().map(AddonClassLoader::getAddon).filter(Objects::nonNull).filter(addon -> addon.getClass().equals(classObject)).map(classObject::cast).findAny(); - } - - /** - * Retrieves an instance of the addon by name. - * - * @param name the name of the addon - * @return {@code Optional} an optional containing the addon instance - */ - public Optional getAddonInstance(@NotNull final String name) { - return Optional.ofNullable(this.loaders.getOrDefault(name, null).getAddon()); - } - - /** - * Reloads an addon. - * - * @param addon the addon instance - * @param the addon instance - * @throws IllegalStateException throws an exception if the addon could not be reloaded. - * @return {@code IAddon} - */ - public IAddon reloadAddon(@NotNull final T addon) throws IllegalStateException { - if (addon.isEnabled()) { - addon.disable(); - } - - final Path path = addon.getLoader().getPath(); - - IAddon foundKey; - - try (AddonClassLoader loader = this.loadAddon(path)) { - final IAddon key = loader.getAddon(); - - if (key != null) { - key.enable(); - } - - foundKey = key; - } catch (final Exception exception) { - throw new IllegalStateException(String.format("Could not reload addon %s!", addon), exception); - } - - return foundKey; - } - - /** - * Unloads an addon. - * - * @param classObject the addon instance - * @param the class instance - * @throws IllegalStateException throws an exception if the addon could not be reloaded. - */ - public void unloadAddon(@NotNull final Class classObject) { - getAddonInstance(classObject).ifPresent(this::unloadAddon); - } - - /** - * Reloads an addon. - * - * @param addon {@link IAddon} - */ - public void reloadAddonConfig(@NotNull final IAddon addon) { - addon.onDisable(); - addon.onEnable(); - } - - /** - * Unloads an addon. - * - * @param addon {@link T} the addon instance - * @param the class instance - * @throws IllegalStateException throws an exception if the addon is not found. - */ - public void unloadAddon(@NotNull final T addon) throws IllegalStateException { - if (addon.isEnabled()) { - addon.disable(); - } - - final AddonClassLoader classLoader = addon.getLoader(); - - classLoader.setDisabling(true); - classLoader.removeClasses(); - - final String name = addon.getName(); - - if (!this.loaders.containsKey(name)) { - throw new IllegalStateException(String.format("Cannot find class loader by name %s, Panicking!", name)); - } - - classLoader.setDisabling(false); - - //noinspection EmptyTryBlock - try (final AddonClassLoader loader = this.loaders.remove(name)) { - - } catch (final IOException ignored) {} - } - - /** - * Loads an addon by path. - * - * @param path the relative path object - * @return {@link AddonClassLoader} the class loader - * @throws IllegalStateException throws an exception if the configuration is invalid, or if an addon already exists with that name. - */ - public @NotNull AddonClassLoader loadAddon(@NotNull final Path path) throws IllegalStateException { - final Addon addon = getProperties(path); - - final String group = addon.getMain(); - final String name = addon.getName(); - - if (group.isEmpty() || group.equals("N/A")) { - throw new IllegalStateException(String.format("Addon %s does not have a group key.", group)); - } - - if (name.isEmpty() || name.equals("N/A")) { - throw new IllegalStateException(String.format("Addon %s does not have a name key.", name)); - } - - if (this.loaders.containsKey(name)) { - throw new IllegalStateException(String.format("Addon with the name %s already been loaded.", name)); - } - - if (this.classMap.containsKey(name)) { - throw new IllegalStateException(String.format("Addon with the name %s main class has already been loaded.", name)); - } - - AddonClassLoader loader; - - try { - loader = new AddonClassLoader(this, path, group, name); - } catch (final Exception exception) { - throw new IllegalStateException(String.format("Failed to load %s!", name), exception); - } - - this.loaders.put(name, loader); - - return loader; - } - - /** - * Get a collection of addons, mapping to getAddon while filtering objects if they are null. - * - * @return {@code Collection} a list of addons - */ - public @NotNull Collection getAddons() { - return this.loaders.values().stream().map(AddonClassLoader::getAddon).filter(Objects::nonNull).toList(); - } - - /** - * Enable all addons. Does NOT load any addons. - * - * @return {@link AddonManager} - */ - public @NotNull AddonManager enableAddons() { - this.loaders.values().stream().map(AddonClassLoader::getAddon).filter(Objects::nonNull).filter(addOn -> !addOn.isEnabled()).forEach(addon -> addon.enable()); - - return this; - } - - /** - * Disable all addons. Does NOT unload them. - * - * @return {@link AddonManager} - */ - public @NotNull AddonManager disableAddons() { - this.loaders.values().stream().map(AddonClassLoader::getAddon).filter(Objects::nonNull).filter(IAddon::isEnabled).forEach(IAddon::disable); - - return this; - } - - /** - * Disables all addons, and purges the caches. - */ - public void purge() { - disableAddons(); - - this.classMap.clear(); - this.loaders.clear(); - } - - /** - * The path of the addon manager. - * - * @return {@link Path} - */ - public @NotNull final Path getPath() { - return this.path; - } - - /** - * Fetches properties using jar file and inputstreams. - * - * @param path the relative path - * @return {@link Addon} - */ - private @NotNull Addon getProperties(@NotNull final Path path) { - final Properties properties = new Properties(); - - try (final FileSystem entry = FileSystems.newFileSystem(path, (ClassLoader) null); final InputStream stream = Files.newInputStream(entry.getPath("addon.properties"))) { - properties.load(stream); - } catch (final IOException exception) { - throw new IllegalStateException("Failed to load addon.properties!", exception); - } - - return new Addon(properties); - } - - /** - * Retrieves a list of paths from the relative path, including the given extensions. - * This method searches up to the specified depth within the directory structure. - * - * @param path the directory to scan for files - * @param extensions the list of file extensions to be searched for (e.g., ".yml") - * @param depth the maximum depth level to search within subdirectories - * @return a list of files - */ - private List getFiles(@NotNull final Path path, @NotNull final List extensions, final int depth) { - final List files = new ArrayList<>(); - - if (Files.isDirectory(path)) { // check if resolved path is a folder to loop through! - try { - Files.walkFileTree(path, new HashSet<>(), Math.max(depth, 1), new SimpleFileVisitor<>() { - @Override - public @NotNull FileVisitResult visitFile(@NotNull final Path path, @NotNull final BasicFileAttributes attributes) { - final String name = path.getFileName().toString(); - - extensions.forEach(extension -> { - if (name.endsWith(extension)) { - files.add(path); - } - }); - - return FileVisitResult.CONTINUE; - } - }); - } catch (final IOException exception) { - throw new IllegalStateException("Failed to get a list of files", exception); - } - } - - return files; - } -} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/ExtensionManager.java similarity index 74% rename from addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java rename to addons/src/main/java/com/ryderbelserion/fusion/addons/ExtensionManager.java index 57f86b2c..da0ab51b 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/ExtensionManager.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/ExtensionManager.java @@ -1,7 +1,8 @@ -package com.ryderbelserion.fusion.addons.v2; +package com.ryderbelserion.fusion.addons; import com.ryderbelserion.fusion.addons.utils.FileUtils; -import com.ryderbelserion.fusion.addons.v2.api.Extension; +import com.ryderbelserion.fusion.addons.api.Extension; +import com.ryderbelserion.fusion.addons.api.interfaces.IExtensionManager; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -13,12 +14,11 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -public class ExtensionManager { +public class ExtensionManager implements IExtensionManager { private final Logger logger = LoggerFactory.getLogger(ExtensionManager.class); private final Map extensions = new ConcurrentHashMap<>(); - private final Map> classMap = new ConcurrentHashMap<>(); private final Path parent; // parent path i.e. the extensions folder @@ -26,6 +26,7 @@ public ExtensionManager(@NotNull final Path parent) { this.parent = parent; } + @Override public void init(final int depth) { try { Files.createDirectories(this.parent); @@ -44,10 +45,11 @@ public void init(final int depth) { this.logger.warn("Initialized {} extension(s)", this.extensions.size()); } + @Override public void loadExtension(@NotNull final Path path) { final Extension extension = new Extension(); - extension.init(this, this.parent, path); + extension.init(this.parent, path); final String name = extension.getName(); @@ -56,17 +58,32 @@ public void loadExtension(@NotNull final Path path) { } this.extensions.put(name, extension); + } + + @Override + public void disableExtension(@NotNull final Extension extension) { + if (!extension.isEnabled()) return; + + extension.onDisable(); - extension.onEnable(); + extension.setEnabled(false); } + @Override public final boolean isExtensionEnabled(@NotNull final String name) { final Optional extension = getExtension(name); return extension.map(Extension::isEnabled).orElse(false); } + @Override public Optional getExtension(@NotNull final String name) { return Optional.ofNullable(this.extensions.get(name)); } + + @Override + public void purge() { + this.extensions.values().forEach(extension -> extension.setEnabled(false)); + this.extensions.clear(); + } } \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/api/Extension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/Extension.java new file mode 100644 index 00000000..09948c60 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/Extension.java @@ -0,0 +1,53 @@ +package com.ryderbelserion.fusion.addons.api; + +import com.ryderbelserion.fusion.addons.api.interfaces.IExtension; +import com.ryderbelserion.fusion.addons.entrypoint.classloaders.SimpleExtensionClassLoader; +import com.ryderbelserion.fusion.addons.exceptions.InvalidExtensionException; +import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.nio.file.Path; + +public class Extension extends IExtension { + + private SimpleExtensionClassLoader classLoader; + + public Extension() {} + + @Override + public void init(@NotNull final Path parent, @NotNull final Path path) { + super.init(parent, path); + + try { + this.classLoader = new SimpleExtensionClassLoader( + path, + parent, + this, + getClass().getClassLoader() + ); + } catch (final IOException | InvalidExtensionException exception) { + throw new RuntimeException(exception); + } + + setEnabled(true); + } + + private boolean isEnabled = false; + + @Override + public void setEnabled(final boolean isEnabled) { + if (this.isEnabled != isEnabled) { + this.isEnabled = isEnabled; + + if (this.isEnabled) { + onEnable(); + } else { + onDisable(); + } + } + } + + @Override + public final boolean isEnabled() { + return this.isEnabled; + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/ExtensionMeta.java similarity index 88% rename from addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java rename to addons/src/main/java/com/ryderbelserion/fusion/addons/api/ExtensionMeta.java index 8424cf14..81541219 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/ExtensionMeta.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/ExtensionMeta.java @@ -1,7 +1,6 @@ -package com.ryderbelserion.fusion.addons.v2.api; +package com.ryderbelserion.fusion.addons.api; -import com.ryderbelserion.fusion.addons.v2.ExtensionManager; -import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtensionMeta; +import com.ryderbelserion.fusion.addons.api.interfaces.IExtensionMeta; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,7 +21,7 @@ public abstract class ExtensionMeta implements IExtensionMeta { private Path path; - public void init(@NotNull final ExtensionManager manager, @NotNull final Path parent, @NotNull final Path path) { + public void init(@NotNull final Path parent, @NotNull final Path path) { final Properties properties = new Properties(); try (final FileSystem entry = FileSystems.newFileSystem(path, (ClassLoader) null); final InputStream stream = Files.newInputStream(entry.getPath("addon.properties"))) { diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtension.java new file mode 100644 index 00000000..89412150 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtension.java @@ -0,0 +1,19 @@ +package com.ryderbelserion.fusion.addons.api.interfaces; + +import com.ryderbelserion.fusion.addons.api.ExtensionMeta; + +public abstract class IExtension extends ExtensionMeta { + + public IExtension() {} + + public void onEnable() {} + + public void onDisable() {} + + public void onReload() {} + + public abstract void setEnabled(final boolean isEnabled); + + public abstract boolean isEnabled(); + +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtensionManager.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtensionManager.java new file mode 100644 index 00000000..eadd595b --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtensionManager.java @@ -0,0 +1,22 @@ +package com.ryderbelserion.fusion.addons.api.interfaces; + +import com.ryderbelserion.fusion.addons.api.Extension; +import org.jetbrains.annotations.NotNull; +import java.nio.file.Path; +import java.util.Optional; + +public interface IExtensionManager { + + void init(final int depth); + + void loadExtension(@NotNull final Path path); + + void disableExtension(@NotNull final Extension extension); + + boolean isExtensionEnabled(@NotNull final String name); + + Optional getExtension(@NotNull final String name); + + void purge(); + +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtensionMeta.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtensionMeta.java similarity index 78% rename from addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtensionMeta.java rename to addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtensionMeta.java index c3e32f98..568ca066 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtensionMeta.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/api/interfaces/IExtensionMeta.java @@ -1,4 +1,4 @@ -package com.ryderbelserion.fusion.addons.v2.api.interfaces; +package com.ryderbelserion.fusion.addons.api.interfaces; import org.slf4j.Logger; import java.nio.file.Path; diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/entrypoint/classloaders/SimpleExtensionClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/entrypoint/classloaders/SimpleExtensionClassLoader.java new file mode 100644 index 00000000..47b8e349 --- /dev/null +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/entrypoint/classloaders/SimpleExtensionClassLoader.java @@ -0,0 +1,89 @@ +package com.ryderbelserion.fusion.addons.entrypoint.classloaders; + +import com.ryderbelserion.fusion.addons.api.interfaces.IExtension; +import com.ryderbelserion.fusion.addons.api.interfaces.IExtensionMeta; +import com.ryderbelserion.fusion.addons.exceptions.InvalidExtensionException; +import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarFile; + +public class SimpleExtensionClassLoader extends URLClassLoader { + + private final Map> classes = new ConcurrentHashMap<>(); + + protected final IExtension extension; + protected final JarFile jarFile; + protected final Path source; + protected final Path path; + protected final URL url; + + public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path source, + @NotNull final IExtensionMeta extension, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { + super(path.getFileName().toString(), new URL[]{path.toUri().toURL()}, loader); + + this.jarFile = new JarFile(path.toFile()); + this.source = source; + this.path = path; + + this.url = this.path.toUri().toURL(); + + Class jarClass; + + try { + jarClass = Class.forName(extension.getMainClass(), true, this); + + this.classes.put(jarClass.getName(), jarClass); + } catch (final ClassNotFoundException exception) { + throw new InvalidExtensionException("Could not find main class %s,".formatted(extension.getMainClass()), exception); + } + + Class extensionClass; + + try { + extensionClass = jarClass.asSubclass(IExtension.class); + } catch (final Exception exception) { + throw new InvalidExtensionException("Main Class %s must extend Extension!".formatted(extension.getMainClass()), exception); + } + + Constructor constructor; + + try { + constructor = extensionClass.getDeclaredConstructor(); + } catch (final NoSuchMethodException exception) { + throw new InvalidExtensionException("Main Class %s must have a no-args constructor".formatted(extension.getMainClass()), exception); + } + + try { + this.extension = constructor.newInstance(); + } catch (InstantiationException e) { + throw new InvalidExtensionException("Main Class %s must not be abstract!".formatted(extension.getMainClass())); + } catch (IllegalAccessException e) { + throw new InvalidExtensionException("Main Class %s must be accessible!".formatted(extension.getMainClass())); + } catch (InvocationTargetException e) { + throw new InvalidExtensionException("Exception initializing main class %s!".formatted(extension.getMainClass())); + } + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + Class result = this.classes.get(name); + + if (result == null) { + this.classes.put(name, result = super.findClass(name)); + } + + return result; + } + + public @NotNull Collection> getClasses() { + return this.classes.values(); + } +} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/exceptions/InvalidExtensionException.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/exceptions/InvalidExtensionException.java similarity index 86% rename from addons/src/main/java/com/ryderbelserion/fusion/addons/v2/exceptions/InvalidExtensionException.java rename to addons/src/main/java/com/ryderbelserion/fusion/addons/exceptions/InvalidExtensionException.java index 8666b4f4..b694dc1e 100644 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/exceptions/InvalidExtensionException.java +++ b/addons/src/main/java/com/ryderbelserion/fusion/addons/exceptions/InvalidExtensionException.java @@ -1,4 +1,4 @@ -package com.ryderbelserion.fusion.addons.v2.exceptions; +package com.ryderbelserion.fusion.addons.exceptions; import org.jetbrains.annotations.NotNull; diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java deleted file mode 100644 index 7ad47abc..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/interfaces/IAddon.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.ryderbelserion.fusion.addons.interfaces; - -import com.ryderbelserion.fusion.addons.AddonClassLoader; -import org.jetbrains.annotations.NotNull; - -/** - * The addon class, handles all things related to addons. - */ -public abstract class IAddon { - - private final AddonClassLoader loader; - private final String name; - - /** - * The addon class, handles all things related to addons. - */ - public IAddon(@NotNull final String name, @NotNull final AddonClassLoader loader, @NotNull final String group) { - this.name = name; - this.loader = loader; - } - - private boolean isEnabled; - - public void onEnable() {} - public void onDisable() {} - public void onReload() {} - - /** - * Enables an addon, this includes adding it to the class path. - */ - public void enable() { - if (this.isEnabled()) { - throw new IllegalStateException("Cannot enable the addon when it's already enabled"); - } - - this.onEnable(); - } - - /** - * Disables the addon, this includes removing it from the class path. - */ - public void disable() { - if (!this.isEnabled()) { - throw new IllegalStateException("Cannot disable the addon when it's not enabled"); - } - - this.onDisable(); - } - - /** - * Retrieves an instance of the addon class loader. - * - * @return {@link AddonClassLoader} - */ - public @NotNull AddonClassLoader getLoader() { - return this.loader; - } - - /** - * Sets the addon to be active. - * - * @param enabled true or false - */ - public void setEnabled(final boolean enabled) { - this.isEnabled = enabled; - } - - /** - * Checks if the addon is enabled or not. - * - * @return true or false - */ - public boolean isEnabled() { - return this.isEnabled; - } - - /** - * Retrieves the name of the addon. - * - * @return the name of the addon - */ - public @NotNull String getName() { - return this.name; - } -} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/objects/Addon.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/objects/Addon.java deleted file mode 100644 index dc40ef29..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/objects/Addon.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.ryderbelserion.fusion.addons.objects; - -import org.jetbrains.annotations.NotNull; -import java.util.Properties; - -/** - * This holds information relating to addons that are created, It pulls information from property files. - */ -public class Addon { - - private final String main; - private final String name; - - /** - * Builds an addon object. - * - * @param properties the properties to pull information from - */ - public Addon(@NotNull final Properties properties) { - this.main = properties.getProperty("main", "N/A"); - this.name = properties.getProperty("name", "N/A"); - } - - /** - * Gets the addon domain i.e. com.ryderbelserion which is the class path. - * - * @return the addon domain - */ - public @NotNull final String getMain() { - return this.main; - } - - /** - * Gets the name of the addon i.e. beans. - * - * @return the name of the addon - */ - public @NotNull final String getName() { - return this.name; - } -} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java deleted file mode 100644 index 14bd4b98..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/Extension.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.ryderbelserion.fusion.addons.v2.api; - -import com.ryderbelserion.fusion.addons.v2.ExtensionManager; -import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtension; -import com.ryderbelserion.fusion.addons.v2.entrypoint.classloaders.SimpleExtensionClassLoader; -import com.ryderbelserion.fusion.addons.v2.exceptions.InvalidExtensionException; -import org.jetbrains.annotations.NotNull; -import java.io.IOException; -import java.nio.file.Path; - -public class Extension extends IExtension { - - private SimpleExtensionClassLoader classLoader; - - public Extension() {} - - @Override - public void init(@NotNull final ExtensionManager manager, @NotNull final Path parent, @NotNull final Path path) { - super.init(manager, parent, path); - - try (final SimpleExtensionClassLoader classLoader = new SimpleExtensionClassLoader(path, parent, this, getClass().getClassLoader())) { - this.classLoader = classLoader; - } catch (final IOException | InvalidExtensionException exception) { - throw new RuntimeException(exception); - } - - setEnabled(true); - } - - public @NotNull final SimpleExtensionClassLoader getClassLoader() { - return this.classLoader; - } - - private boolean isEnabled = false; - - @Override - public void setEnabled(final boolean isEnabled) { - this.isEnabled = isEnabled; - } - - @Override - public final boolean isEnabled() { - return this.isEnabled; - } -} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java deleted file mode 100644 index 3f70386c..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/api/interfaces/IExtension.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ryderbelserion.fusion.addons.v2.api.interfaces; - -import com.ryderbelserion.fusion.addons.v2.api.ExtensionMeta; - -public abstract class IExtension extends ExtensionMeta { - - public IExtension() { - - } - - public void onEnable() { - - } - - public void onDisable() { - - } - - public void onReload() { - - } - - public abstract void setEnabled(final boolean isEnabled); - - public abstract boolean isEnabled(); - -} \ No newline at end of file diff --git a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java b/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java deleted file mode 100644 index bc3cff70..00000000 --- a/addons/src/main/java/com/ryderbelserion/fusion/addons/v2/entrypoint/classloaders/SimpleExtensionClassLoader.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.ryderbelserion.fusion.addons.v2.entrypoint.classloaders; - -import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtension; -import com.ryderbelserion.fusion.addons.v2.api.interfaces.IExtensionMeta; -import com.ryderbelserion.fusion.addons.v2.exceptions.InvalidExtensionException; -import org.jetbrains.annotations.NotNull; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Path; -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.jar.JarFile; - -public class SimpleExtensionClassLoader extends URLClassLoader { - - private final Map> classes = new ConcurrentHashMap<>(); - - protected final IExtensionMeta config; - protected final IExtension extension; - protected final JarFile jarFile; - protected final Path source; - protected final Path path; - protected final URL url; - - public SimpleExtensionClassLoader(@NotNull final Path path, @NotNull final Path source, - @NotNull final IExtensionMeta config, @NotNull final ClassLoader loader) throws IOException, InvalidExtensionException { - super(path.getFileName().toString(), new URL[]{path.toUri().toURL()}, loader); - - this.jarFile = new JarFile(path.toFile()); - this.source = source; - this.config = config; - this.path = path; - - this.url = this.path.toUri().toURL(); - - Class mainClass; - - try { - mainClass = Class.forName(this.config.getMainClass(), true, this); - - this.classes.put(mainClass.getName(), mainClass); - } catch (final ClassNotFoundException exception) { - throw new InvalidExtensionException("Could not find main class %s,".formatted(this.config.getMainClass()), exception); - } - - Class extension; - - try { - extension = mainClass.asSubclass(IExtension.class); - } catch (final Exception exception) { - throw new InvalidExtensionException("Main Class %s must extend Extension!".formatted(this.config.getMainClass()), exception); - } - - try { - this.extension = extension.getDeclaredConstructor().newInstance(); - } catch (final IllegalAccessException | NoSuchMethodException | InstantiationException | - InvocationTargetException exception) { - throw new InvalidExtensionException("Failed to load main class for the extension %s!".formatted(this.config.getName()), exception); - } - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - Class result = this.classes.get(name); - - if (result == null) { - this.classes.put(name, result = super.findClass(name)); - } - - return result; - } - - public @NotNull Collection> getClasses() { - return this.classes.values(); - } -} \ No newline at end of file diff --git a/examples/extension/src/main/java/me/corecraft/currency/Currency.java b/examples/extension/src/main/java/me/corecraft/currency/Currency.java index 911e82cc..8a407261 100644 --- a/examples/extension/src/main/java/me/corecraft/currency/Currency.java +++ b/examples/extension/src/main/java/me/corecraft/currency/Currency.java @@ -1,6 +1,6 @@ package me.corecraft.currency; -import com.ryderbelserion.fusion.addons.v2.api.Extension; +import com.ryderbelserion.fusion.addons.api.Extension; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -18,11 +18,13 @@ public void onEnable() { throw new IllegalStateException("Cannot enable the extension, the folder %s did not get created.".formatted(path)); } } + + getLogger().warn("The extension is enabling!"); } @Override public void onDisable() { - + getLogger().warn("The extension is disabled!"); } @Override diff --git a/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java b/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java index e240c8df..c1326d12 100644 --- a/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java +++ b/plugin/src/main/java/com/ryderbelserion/fusion/Fusion.java @@ -1,6 +1,6 @@ package com.ryderbelserion.fusion; -import com.ryderbelserion.fusion.addons.v2.ExtensionManager; +import com.ryderbelserion.fusion.addons.ExtensionManager; import com.ryderbelserion.fusion.paper.FusionPaper; import com.ryderbelserion.fusion.paper.builders.ItemBuilder; import org.bukkit.entity.Player; @@ -14,11 +14,11 @@ public class Fusion extends JavaPlugin implements Listener { - private final FusionPaper fusion; + //private final FusionPaper fusion; - public Fusion(@NotNull final FusionPaper fusion) { - this.fusion = fusion; - } + //public Fusion(@NotNull final FusionPaper fusion) { + // //this.fusion = fusion; + //} @Override public void onEnable() { @@ -26,9 +26,9 @@ public void onEnable() { manager.init(1); - this.fusion.setPlugin(this).init(); + //this.fusion.setPlugin(this).init(); - getServer().getPluginManager().registerEvents(this, this); + //getServer().getPluginManager().registerEvents(this, this); } @EventHandler diff --git a/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java b/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java index b5d5e7e9..be0b850a 100644 --- a/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java +++ b/plugin/src/main/java/com/ryderbelserion/fusion/FusionLoader.java @@ -25,6 +25,6 @@ public void bootstrap(@NotNull BootstrapContext context) { @Override public @NotNull JavaPlugin createPlugin(@NotNull PluginProviderContext context) { - return new Fusion(this.fusion); + return new Fusion(); } } \ No newline at end of file diff --git a/plugin/src/main/resources/paper-plugin.yml b/plugin/src/main/resources/paper-plugin.yml index d9990d4f..757a2cf0 100644 --- a/plugin/src/main/resources/paper-plugin.yml +++ b/plugin/src/main/resources/paper-plugin.yml @@ -1,6 +1,6 @@ name: 'Fusion' main: 'com.ryderbelserion.fusion.Fusion' -bootstrapper: 'com.ryderbelserion.fusion.FusionLoader' +#bootstrapper: 'com.ryderbelserion.fusion.FusionLoader' version: '1.0.0' api-version: '1.21.10'