From 6915dee3e3a5f2c00f32116019fc652d64349d41 Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Sun, 19 Feb 2023 09:57:10 -0500 Subject: [PATCH] Paper Plugins (#8108) --- build.gradle.kts | 2 +- ...Add-Raw-Byte-ItemStack-Serialization.patch | 4 +- ...dd-an-asterisk-to-legacy-API-plugins.patch | 66 - ...-option-to-load-extra-plugin-jars-no.patch | 109 +- ...eload-permissions.yml-and-require-co.patch | 18 - .../api/Add-exception-reporting-event.patch | 2 +- ...rty-to-print-stacktrace-on-bad-plugi.patch | 2 +- ...or-plugins-modifying-the-parent-of-t.patch | 48 +- patches/api/Adventure.patch | 3 +- ...so-load-resources-from-LibraryLoader.patch | 6 +- ...ly-disable-plugins-that-fail-to-load.patch | 21 - ...lose-Plugin-Class-Loaders-on-Disable.patch | 105 - ...nts-firing-Async-errors-during-shutd.patch | 25 - ...n-t-load-plugins-prefixed-with-a-dot.patch | 18 - .../Enable-multi-release-plugin-jars.patch | 2 +- .../api/Fix-plugin-provides-load-order.patch | 27 - patches/api/Future-API-Plans.patch | 67 - ...efixes-in-implementation-logging-con.patch | 6 +- ...-missing-hard-depends-not-just-first.patch | 91 - .../Make-JavaPluginLoader-thread-safe.patch | 53 - .../api/Make-plugins-list-alphabetical.patch | 56 - patches/api/Paper-Plugins.patch | 2494 ++++++ ...rioritise-own-classes-where-possible.patch | 86 - ...-a-useful-PluginClassLoader-toString.patch | 30 - ...deadlock-risk-in-firing-async-events.patch | 124 - ...s-to-contain-the-source-jars-in-stac.patch | 12 +- patches/api/Test-changes.patch | 35 + patches/api/Timings-v2.patch | 22 +- .../api/Update-Folder-Uses-Plugin-Name.patch | 86 - patches/api/Use-ASM-for-event-executors.patch | 27 - ...ftMagicNumbers.isSupportedApiVersion.patch | 22 - ...Add-Raw-Byte-ItemStack-Serialization.patch | 4 +- ...-option-to-load-extra-plugin-jars-no.patch | 21 +- .../Add-debug-for-sync-chunk-loads.patch | 4 +- .../Flat-bedrock-generator-settings.patch | 33 +- patches/server/Paper-Plugins.patch | 6780 +++++++++++++++++ patches/server/Rewrite-chunk-system.patch | 2 +- patches/server/Starlight.patch | 2 +- patches/server/Timings-v2.patch | 2 +- test-plugin/build.gradle.kts | 2 +- .../{paper => }/testplugin/TestPlugin.java | 3 +- .../testplugin/TestPluginBootstrap.java | 13 + .../papermc/testplugin/TestPluginLoader.java | 11 + .../src/main/resources/paper-plugin.yml | 12 + test-plugin/src/main/resources/plugin.yml | 7 - 45 files changed, 9426 insertions(+), 1139 deletions(-) delete mode 100644 patches/api/Add-an-asterisk-to-legacy-API-plugins.patch delete mode 100644 patches/api/Automatically-disable-plugins-that-fail-to-load.patch delete mode 100644 patches/api/Close-Plugin-Class-Loaders-on-Disable.patch delete mode 100644 patches/api/Disable-Sync-Events-firing-Async-errors-during-shutd.patch delete mode 100644 patches/api/Don-t-load-plugins-prefixed-with-a-dot.patch delete mode 100644 patches/api/Fix-plugin-provides-load-order.patch delete mode 100644 patches/api/Future-API-Plans.patch delete mode 100644 patches/api/List-all-missing-hard-depends-not-just-first.patch delete mode 100644 patches/api/Make-JavaPluginLoader-thread-safe.patch delete mode 100644 patches/api/Make-plugins-list-alphabetical.patch create mode 100644 patches/api/Paper-Plugins.patch delete mode 100644 patches/api/Prioritise-own-classes-where-possible.patch delete mode 100644 patches/api/Provide-a-useful-PluginClassLoader-toString.patch delete mode 100644 patches/api/Remove-deadlock-risk-in-firing-async-events.patch delete mode 100644 patches/api/Update-Folder-Uses-Plugin-Name.patch delete mode 100644 patches/server/Add-CraftMagicNumbers.isSupportedApiVersion.patch create mode 100644 patches/server/Paper-Plugins.patch rename test-plugin/src/main/java/io/papermc/{paper => }/testplugin/TestPlugin.java (88%) create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java create mode 100644 test-plugin/src/main/java/io/papermc/testplugin/TestPluginLoader.java create mode 100644 test-plugin/src/main/resources/paper-plugin.yml delete mode 100644 test-plugin/src/main/resources/plugin.yml diff --git a/build.gradle.kts b/build.gradle.kts index 0f7fd3dbda..d3c530db0b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -39,7 +39,7 @@ subprojects { events(TestLogEvent.STANDARD_OUT) } minHeapSize = "2g" - maxHeapSize = "2g" + maxHeapSize = "4g" } repositories { diff --git a/patches/api/Add-Raw-Byte-ItemStack-Serialization.patch b/patches/api/Add-Raw-Byte-ItemStack-Serialization.patch index bb89536915..d30beb0b77 100644 --- a/patches/api/Add-Raw-Byte-ItemStack-Serialization.patch +++ b/patches/api/Add-Raw-Byte-ItemStack-Serialization.patch @@ -10,8 +10,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/org/bukkit/UnsafeValues.java +++ b/src/main/java/org/bukkit/UnsafeValues.java @@ -0,0 +0,0 @@ public interface UnsafeValues { - static boolean isLegacyPlugin(org.bukkit.plugin.Plugin plugin) { - return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion()); + default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); } + + byte[] serializeItem(ItemStack item); diff --git a/patches/api/Add-an-asterisk-to-legacy-API-plugins.patch b/patches/api/Add-an-asterisk-to-legacy-API-plugins.patch deleted file mode 100644 index 0f936372dd..0000000000 --- a/patches/api/Add-an-asterisk-to-legacy-API-plugins.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Phoenix616 <max@themoep.de> -Date: Tue, 1 Dec 2020 14:57:02 +0100 -Subject: [PATCH] Add an asterisk to legacy API plugins - -Not here to name and shame, only so server admins can be aware of which -plugins have and haven't been updated. - -diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/UnsafeValues.java -+++ b/src/main/java/org/bukkit/UnsafeValues.java -@@ -0,0 +0,0 @@ public interface UnsafeValues { - default com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { - return new com.destroystokyo.paper.util.VersionFetcher.DummyVersionFetcher(); - } -+ -+ boolean isSupportedApiVersion(String apiVersion); -+ -+ static boolean isLegacyPlugin(org.bukkit.plugin.Plugin plugin) { -+ return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion()); -+ } - // Paper end - } -diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java -+++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java -@@ -0,0 +0,0 @@ public class PluginsCommand extends BukkitCommand { - } - - Plugin plugin = entry.getValue(); -- -+ - pluginList.append(plugin.isEnabled() ? ChatColor.GREEN : ChatColor.RED); -- pluginList.append(plugin.getDescription().getName()); -+ // Paper start - Add an asterisk to legacy plugins (so admins are aware) -+ String pluginName = plugin.getDescription().getName(); -+ if (org.bukkit.UnsafeValues.isLegacyPlugin(plugin)) { -+ pluginName += "*"; -+ } -+ pluginList.append(pluginName); -+ // Paper end - - if (plugin.getDescription().getProvides().size() > 0) { - pluginList.append(" (").append(String.join(", ", plugin.getDescription().getProvides())).append(")"); -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - Preconditions.checkArgument(plugin instanceof JavaPlugin, "Plugin is not associated with this PluginLoader"); - - if (!plugin.isEnabled()) { -- plugin.getLogger().info("Enabling " + plugin.getDescription().getFullName()); -+ // Paper start - Add an asterisk to legacy plugins (so admins are aware) -+ String enableMsg = "Enabling " + plugin.getDescription().getFullName(); -+ if (org.bukkit.UnsafeValues.isLegacyPlugin(plugin)) { -+ enableMsg += "*"; -+ } -+ -+ plugin.getLogger().info(enableMsg); -+ // Paper end - - JavaPlugin jPlugin = (JavaPlugin) plugin; - diff --git a/patches/api/Add-command-line-option-to-load-extra-plugin-jars-no.patch b/patches/api/Add-command-line-option-to-load-extra-plugin-jars-no.patch index 0d057f5045..b7f88bd3af 100644 --- a/patches/api/Add-command-line-option-to-load-extra-plugin-jars-no.patch +++ b/patches/api/Add-command-line-option-to-load-extra-plugin-jars-no.patch @@ -68,104 +68,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @NotNull + public Plugin[] loadPlugins(final @NotNull File directory, final @NotNull List<File> extraPluginJars) { + // Paper end + if (true) { + List<Plugin> pluginList = new ArrayList<>(); + java.util.Collections.addAll(pluginList, this.paperPluginManager.loadPlugins(directory)); ++ for (File file : extraPluginJars) { ++ try { ++ pluginList.add(this.paperPluginManager.loadPlugin(file)); ++ } catch (Exception e) { ++ this.server.getLogger().log(Level.SEVERE, "Plugin loading error!", e); ++ } ++ } + return pluginList.toArray(new Plugin[0]); + } Preconditions.checkArgument(directory != null, "Directory cannot be null"); - Preconditions.checkArgument(directory.isDirectory(), "Directory must be a directory"); - -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - Map<String, Collection<String>> softDependencies = new HashMap<String, Collection<String>>(); - - // This is where it figures out all possible plugins -- for (File file : directory.listFiles()) { -+ // Paper start - extra jars -+ final List<File> pluginJars = new ArrayList<>(java.util.Arrays.asList(directory.listFiles())); -+ pluginJars.addAll(extraPluginJars); -+ for (File file : pluginJars) { -+ // Paper end - PluginLoader loader = null; - for (Pattern filter : filters) { - Matcher match = filter.matcher(file.getName()); -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - description = loader.getPluginDescription(file); - String name = description.getName(); - if (name.equalsIgnoreCase("bukkit") || name.equalsIgnoreCase("minecraft") || name.equalsIgnoreCase("mojang")) { -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': Restricted Name"); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "': Restricted Name"); // Paper - continue; - } else if (description.rawName.indexOf(' ') != -1) { -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': uses the space-character (0x20) in its name"); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "': uses the space-character (0x20) in its name"); // Paper - continue; - } - } catch (InvalidDescriptionException ex) { -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "'", ex); // Paper - continue; - } - -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - description.getName(), - file.getPath(), - replacedFile.getPath(), -- directory.getPath() -+ file.getParentFile().getPath() // Paper - )); - } - -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - file.getPath(), - provided, - pluginFile.getPath(), -- directory.getPath() -+ file.getParentFile().getPath() // Paper - )); - } else { - String replacedPlugin = pluginsProvided.put(provided, description.getName()); -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - - server.getLogger().log( - Level.SEVERE, -- "Could not load '" + entry.getValue().getPath() + "' in folder '" + directory.getPath() + "'", -+ "Could not load '" + entry.getValue().getPath() + "' in folder '" + entry.getValue().getParentFile().getPath() + "'", // Paper - new UnknownDependencyException("Unknown dependency " + dependency + ". Please download and install " + dependency + " to run this plugin.")); - break; - } -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - loadedPlugins.add(loadedPlugin.getName()); - loadedPlugins.addAll(loadedPlugin.getDescription().getProvides()); - } else { -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'"); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "'"); // Paper - } - continue; - } catch (InvalidPluginException ex) { -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "'", ex); // Paper - } - } - } -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - loadedPlugins.add(loadedPlugin.getName()); - loadedPlugins.addAll(loadedPlugin.getDescription().getProvides()); - } else { -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'"); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "'"); // Paper - } - break; - } catch (InvalidPluginException ex) { -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "'", ex); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "'", ex); // Paper - } - } - } -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - while (failedPluginIterator.hasNext()) { - File file = failedPluginIterator.next(); - failedPluginIterator.remove(); -- server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + directory.getPath() + "': circular dependency detected"); -+ server.getLogger().log(Level.SEVERE, "Could not load '" + file.getPath() + "' in folder '" + file.getParentFile().getPath() + "': circular dependency detected"); // Paper - } - } - } diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java diff --git a/patches/api/Add-command-to-reload-permissions.yml-and-require-co.patch b/patches/api/Add-command-to-reload-permissions.yml-and-require-co.patch index d74beb5762..7addea4a6c 100644 --- a/patches/api/Add-command-to-reload-permissions.yml-and-require-co.patch +++ b/patches/api/Add-command-to-reload-permissions.yml-and-require-co.patch @@ -84,21 +84,3 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return java.util.Collections.singletonList("permissions"); // Paper } } -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - public void useTimings(boolean use) { - co.aikar.timings.Timings.setTimingsEnabled(use); // Paper - } -+ -+ // Paper start -+ public void clearPermissions() { -+ permissions.clear(); -+ defaultPerms.get(true).clear(); -+ defaultPerms.get(false).clear(); -+ } -+ // Paper end -+ - } diff --git a/patches/api/Add-exception-reporting-event.patch b/patches/api/Add-exception-reporting-event.patch index 47a6b652ec..89475e4f38 100644 --- a/patches/api/Add-exception-reporting-event.patch +++ b/patches/api/Add-exception-reporting-event.patch @@ -562,7 +562,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + @Override public void clearPlugins() { - synchronized (this) { + if (true) {this.paperPluginManager.clearPlugins(); return;} // Paper @@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { )); } diff --git a/patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch b/patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch index b724857c38..c9371793dd 100644 --- a/patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch +++ b/patches/api/Add-system-property-to-print-stacktrace-on-bad-plugi.patch @@ -9,7 +9,7 @@ diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot +@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm // In case the bad access occurs on construction loader.server.getLogger().log(Level.WARNING, "[{0}] Loaded class {1} from {2} which is not a depend or softdepend of this plugin.", new Object[]{description.getName(), name, provider.getFullName()}); } diff --git a/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch b/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch index 2406cc6426..feb529d95d 100644 --- a/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch +++ b/patches/api/Add-workaround-for-plugins-modifying-the-parent-of-t.patch @@ -20,6 +20,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ +package com.destroystokyo.paper.utils; + ++import io.papermc.paper.plugin.configuration.PluginMeta; +import org.bukkit.plugin.PluginDescriptionFile; + +import java.util.logging.Level; @@ -32,20 +33,26 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + */ +public class PaperPluginLogger extends Logger { + ++ @Deprecated(forRemoval = true) + @NotNull + public static Logger getLogger(@NotNull PluginDescriptionFile description) { -+ Logger logger = new PaperPluginLogger(description); ++ return getLogger((PluginMeta) description); ++ } ++ ++ @NotNull ++ public static Logger getLogger(@NotNull PluginMeta meta) { ++ Logger logger = new PaperPluginLogger(meta); + if (!LogManager.getLogManager().addLogger(logger)) { + // Disable this if it's going to happen across reloads anyways... + //logger.log(Level.WARNING, "Could not insert plugin logger - one was already found: {}", LogManager.getLogManager().getLogger(this.getName())); -+ logger = LogManager.getLogManager().getLogger(description.getPrefix() != null ? description.getPrefix() : description.getName()); ++ logger = LogManager.getLogManager().getLogger(meta.getLoggerPrefix() != null ? meta.getLoggerPrefix() : meta.getName()); + } + + return logger; + } + -+ private PaperPluginLogger(@NotNull PluginDescriptionFile description) { -+ super(description.getPrefix() != null ? description.getPrefix() : description.getName(), null); ++ private PaperPluginLogger(@NotNull PluginMeta meta) { ++ super(meta.getLoggerPrefix() != null ? meta.getLoggerPrefix() : meta.getName(), null); + } + + @Override @@ -68,16 +75,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private FileConfiguration newConfig = null; private File configFile = null; - private Logger logger = null; // Paper - PluginLogger -> Logger -+ Logger logger = null; // Paper - PluginLogger -> Logger, package-private ++ public Logger logger = null; // Paper - PluginLogger -> Logger, public public JavaPlugin() { - final ClassLoader classLoader = this.getClass().getClassLoader(); + // Paper start @@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { - this.dataFolder = dataFolder; this.classLoader = classLoader; this.configFile = new File(dataFolder, "config.yml"); -- // Paper - Handle plugin prefix in implementation -- this.logger = Logger.getLogger(description.getPrefix() != null ? description.getPrefix() : description.getName()); + this.pluginMeta = configuration; // Paper +- this.logger = Logger.getLogger(description.getPrefix() != null ? description.getPrefix() : description.getName()); // Paper - Handle plugin prefix in implementation + // Paper start + if (this.logger == null) { + this.logger = com.destroystokyo.paper.utils.PaperPluginLogger.getLogger(this.description); @@ -90,28 +96,20 @@ diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot - private JavaPlugin pluginInit; - private IllegalStateException pluginState; - private final Set<String> seenIllegalAccess = Collections.newSetFromMap(new ConcurrentHashMap<>()); -+ private java.util.logging.Logger logger; // Paper - add field - - static { - ClassLoader.registerAsParallelCapable(); -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot +@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm this.url = file.toURI().toURL(); this.libraryLoader = libraryLoader; +- + this.logger = com.destroystokyo.paper.utils.PaperPluginLogger.getLogger(description); // Paper - Register logger early -+ - try { - Class<?> jarClass; - try { -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot + // Paper start + this.classLoaderGroup = io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage.instance().registerSpigotGroup(this); // Paper + this.dependencyContext = dependencyContext; +@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm pluginState = new IllegalStateException("Initial initialization"); this.pluginInit = javaPlugin; + javaPlugin.logger = this.logger; // Paper - set logger - javaPlugin.init(loader, loader.server, description, dataFolder, file, this); + javaPlugin.init(null, org.bukkit.Bukkit.getServer(), description, dataFolder, file, this); // Paper } - } + diff --git a/patches/api/Adventure.patch b/patches/api/Adventure.patch index 175246c58c..26af193a6c 100644 --- a/patches/api/Adventure.patch +++ b/patches/api/Adventure.patch @@ -1569,11 +1569,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 protected String usageMessage; private String permission; - private String permissionMessage; -- public org.spigotmc.CustomTimingsHandler timings; // Spigot + private net.kyori.adventure.text.Component permissionMessage; // Paper + public org.spigotmc.CustomTimingsHandler timings; // Spigot protected Command(@NotNull String name) { - this(name, "", "/" + name, new ArrayList<String>()); @@ -0,0 +0,0 @@ public abstract class Command { if (permissionMessage == null) { diff --git a/patches/api/Also-load-resources-from-LibraryLoader.patch b/patches/api/Also-load-resources-from-LibraryLoader.patch index 8457d39fe3..bc1cd06028 100644 --- a/patches/api/Also-load-resources-from-LibraryLoader.patch +++ b/patches/api/Also-load-resources-from-LibraryLoader.patch @@ -8,7 +8,7 @@ diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot +@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm @Override public URL getResource(String name) { @@ -43,6 +43,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end + + // Paper start @Override - protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { - return loadClass0(name, resolve, true, true); + public Class<?> loadClass(@NotNull String name, boolean resolve, boolean checkGlobal, boolean checkLibraries) throws ClassNotFoundException { diff --git a/patches/api/Automatically-disable-plugins-that-fail-to-load.patch b/patches/api/Automatically-disable-plugins-that-fail-to-load.patch deleted file mode 100644 index f957932d0a..0000000000 --- a/patches/api/Automatically-disable-plugins-that-fail-to-load.patch +++ /dev/null @@ -1,21 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar <aikar@aikar.co> -Date: Mon, 29 Feb 2016 19:45:21 -0600 -Subject: [PATCH] Automatically disable plugins that fail to load - - -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - jPlugin.setEnabled(true); - } catch (Throwable ex) { - server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); -+ // Paper start - Disable plugins that fail to load -+ this.server.getPluginManager().disablePlugin(jPlugin); -+ return; -+ // Paper end - } - - // Perhaps abort here, rather than continue going, but as it stands, diff --git a/patches/api/Close-Plugin-Class-Loaders-on-Disable.patch b/patches/api/Close-Plugin-Class-Loaders-on-Disable.patch deleted file mode 100644 index accef0b3c3..0000000000 --- a/patches/api/Close-Plugin-Class-Loaders-on-Disable.patch +++ /dev/null @@ -1,105 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar <aikar@aikar.co> -Date: Tue, 1 May 2018 21:33:35 -0400 -Subject: [PATCH] Close Plugin Class Loaders on Disable - -This should close more memory leaks from /reload and disabling plugins, -by closing the class loader and the jar file. - -Note: This patch is no longer necessary as upstream now also closes -PluginClassLoaders on disable. This patch is now only to keep around -API methods it previously added, and to add back the log message when a -PluginClassLoader fails to disable, as upstream decided to ignore the -exception. - -diff --git a/src/main/java/org/bukkit/plugin/PluginLoader.java b/src/main/java/org/bukkit/plugin/PluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/PluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/PluginLoader.java -@@ -0,0 +0,0 @@ public interface PluginLoader { - * @param plugin Plugin to disable - */ - public void disablePlugin(@NotNull Plugin plugin); -+ -+ // Paper start - close Classloader on disable -+ /** -+ * This method is no longer useful as upstream has -+ * made it so plugin classloaders are always closed on disable. -+ * Use {@link #disablePlugin(Plugin)} instead. -+ * -+ * @param plugin Plugin to disable -+ * @param closeClassloader unused -+ * @deprecated Classloader is always closed by upstream now. -+ */ -+ @Deprecated(forRemoval = true) -+ // provide default to allow other PluginLoader implementations to work -+ default public void disablePlugin(@NotNull Plugin plugin, boolean closeClassloader) { -+ disablePlugin(plugin); -+ } -+ // Paper end - close Classloader on disable - } -diff --git a/src/main/java/org/bukkit/plugin/PluginManager.java b/src/main/java/org/bukkit/plugin/PluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/PluginManager.java -+++ b/src/main/java/org/bukkit/plugin/PluginManager.java -@@ -0,0 +0,0 @@ public interface PluginManager { - */ - public void disablePlugin(@NotNull Plugin plugin); - -+ // Paper start - close Classloader on disable -+ /** -+ * This method is no longer useful as upstream has -+ * made it so plugin classloaders are always closed on disable. -+ * Use {@link #disablePlugin(Plugin)} instead. -+ * -+ * @param plugin Plugin to disable -+ * @param closeClassloader unused -+ * @deprecated Classloader is always closed by upstream now. -+ */ -+ @Deprecated(forRemoval = true) -+ public default void disablePlugin(@NotNull Plugin plugin, boolean closeClassloader) { -+ this.disablePlugin(plugin); -+ } -+ // Paper end - close Classloader on disable -+ - /** - * Gets a {@link Permission} from its fully qualified name - * -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - } - } - -+ // Paper start -+ /** -+ * This method is no longer useful as upstream has -+ * made it so plugin classloaders are always closed on disable. -+ * Use {@link #disablePlugins()} instead. -+ * -+ * @param closeClassloaders unused -+ * @deprecated Classloader is always closed by upstream now. -+ */ -+ @Deprecated(forRemoval = true) -+ public void disablePlugins(boolean closeClassloaders) { -+ this.disablePlugins(); -+ } -+ // Paper end -+ - @Override - public void disablePlugin(@NotNull final Plugin plugin) { - if (plugin.isEnabled()) { -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - loader.close(); - } catch (IOException ex) { - // -+ this.server.getLogger().log(Level.WARNING, "Error closing the PluginClassLoader for '" + plugin.getDescription().getFullName() + "'", ex); // Paper - log exception - } - } - } diff --git a/patches/api/Disable-Sync-Events-firing-Async-errors-during-shutd.patch b/patches/api/Disable-Sync-Events-firing-Async-errors-during-shutd.patch deleted file mode 100644 index 56f396dc00..0000000000 --- a/patches/api/Disable-Sync-Events-firing-Async-errors-during-shutd.patch +++ /dev/null @@ -1,25 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar <aikar@aikar.co> -Date: Sat, 11 Apr 2020 21:38:59 -0400 -Subject: [PATCH] Disable Sync Events firing Async errors during shutdown - -This is how it use to behave on Paper, and this is totally destroying -the ability to try to shut the server down gracefully during the -shutdown process as events firing on the watchdog thread are throwing -errors. - -This isn't an issue on Spigot - -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - // Paper - replace callEvent by merging to below method - if (event.isAsynchronous() && server.isPrimaryThread()) { - throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); -- } else if (!event.isAsynchronous() && !server.isPrimaryThread()) { -+ } else if (!event.isAsynchronous() && !server.isPrimaryThread() && !server.isStopping() ) { - throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); - } - diff --git a/patches/api/Don-t-load-plugins-prefixed-with-a-dot.patch b/patches/api/Don-t-load-plugins-prefixed-with-a-dot.patch deleted file mode 100644 index faf72711b0..0000000000 --- a/patches/api/Don-t-load-plugins-prefixed-with-a-dot.patch +++ /dev/null @@ -1,18 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Noah van der Aa <ndvdaa@gmail.com> -Date: Sat, 22 Jan 2022 16:35:44 +0100 -Subject: [PATCH] Don't load plugins prefixed with a dot - - -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - final List<File> pluginJars = new ArrayList<>(java.util.Arrays.asList(directory.listFiles())); - pluginJars.addAll(extraPluginJars); - for (File file : pluginJars) { -+ if (file.getName().startsWith(".") && !extraPluginJars.contains(file)) continue; // Don't load plugin if the file name starts with a dot, except if it's a extra plugin jar. - // Paper end - PluginLoader loader = null; - for (Pattern filter : filters) { diff --git a/patches/api/Enable-multi-release-plugin-jars.patch b/patches/api/Enable-multi-release-plugin-jars.patch index a9230e6211..21890c1e89 100644 --- a/patches/api/Enable-multi-release-plugin-jars.patch +++ b/patches/api/Enable-multi-release-plugin-jars.patch @@ -8,7 +8,7 @@ diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot +@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm this.description = description; this.dataFolder = dataFolder; this.file = file; diff --git a/patches/api/Fix-plugin-provides-load-order.patch b/patches/api/Fix-plugin-provides-load-order.patch deleted file mode 100644 index 8068cedb1c..0000000000 --- a/patches/api/Fix-plugin-provides-load-order.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke <nassim@njahnke.dev> -Date: Fri, 1 Oct 2021 09:47:00 +0200 -Subject: [PATCH] Fix plugin provides load order - -Fixes https://hub.spigotmc.org/jira/browse/SPIGOT-6740 - -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - // Paper end - missingDependency = false; - pluginIterator.remove(); -+ pluginsProvided.values().removeIf(s -> s.equals(plugin)); // Paper - remove provided plugins - softDependencies.remove(plugin); - dependencies.remove(plugin); - -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - // We're clear to load, no more soft or hard dependencies left - File file = plugins.get(plugin); - pluginIterator.remove(); -+ pluginsProvided.values().removeIf(s -> s.equals(plugin)); // Paper - remove provided plugins - missingDependency = false; - - try { diff --git a/patches/api/Future-API-Plans.patch b/patches/api/Future-API-Plans.patch deleted file mode 100644 index 1fae1cab9c..0000000000 --- a/patches/api/Future-API-Plans.patch +++ /dev/null @@ -1,67 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> -Date: Wed, 7 Dec 2022 19:12:54 -0500 -Subject: [PATCH] Future API Plans - - -diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/Plugin.java -+++ b/src/main/java/org/bukkit/plugin/Plugin.java -@@ -0,0 +0,0 @@ public interface Plugin extends TabExecutor { - * - * @return PluginLoader that controls this plugin - */ -+ @Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future - @NotNull - public PluginLoader getPluginLoader(); - -diff --git a/src/main/java/org/bukkit/plugin/PluginLoader.java b/src/main/java/org/bukkit/plugin/PluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/PluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/PluginLoader.java -@@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull; - * Represents a plugin loader, which handles direct access to specific types - * of plugins - */ -+@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future - public interface PluginLoader { - - /** -diff --git a/src/main/java/org/bukkit/plugin/PluginManager.java b/src/main/java/org/bukkit/plugin/PluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/PluginManager.java -+++ b/src/main/java/org/bukkit/plugin/PluginManager.java -@@ -0,0 +0,0 @@ public interface PluginManager { - * @throws IllegalArgumentException Thrown when the given Class is not a - * valid PluginLoader - */ -+ @Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future - public void registerInterface(@NotNull Class<? extends PluginLoader> loader) throws IllegalArgumentException; - - /** -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ import org.jetbrains.annotations.Nullable; - /** - * Handles all plugin management from the Server - */ -+@Deprecated(forRemoval = true) // Paper - This implementation may be replaced in a future version of Paper. -+// Plugins may still reflect into this class to modify permission logic for the time being. - public final class SimplePluginManager implements PluginManager { - private final Server server; - private final Map<Pattern, PluginLoader> fileAssociations = new HashMap<Pattern, PluginLoader>(); -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ import org.yaml.snakeyaml.error.YAMLException; - /** - * Represents a Java plugin loader, allowing plugins in the form of .jar - */ -+@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future. This implementation will be moved. - public final class JavaPluginLoader implements PluginLoader { - final Server server; - private static final boolean DISABLE_CLASS_PRIORITIZATION = Boolean.getBoolean("Paper.DisableClassPrioritization"); // Paper diff --git a/patches/api/Handle-plugin-prefixes-in-implementation-logging-con.patch b/patches/api/Handle-plugin-prefixes-in-implementation-logging-con.patch index 423278a7a5..1877c6a79c 100644 --- a/patches/api/Handle-plugin-prefixes-in-implementation-logging-con.patch +++ b/patches/api/Handle-plugin-prefixes-in-implementation-logging-con.patch @@ -28,14 +28,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + private Logger logger = null; // Paper - PluginLogger -> Logger public JavaPlugin() { - final ClassLoader classLoader = this.getClass().getClassLoader(); + // Paper start @@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { this.dataFolder = dataFolder; this.classLoader = classLoader; this.configFile = new File(dataFolder, "config.yml"); - this.logger = new PluginLogger(this); -+ // Paper - Handle plugin prefix in implementation -+ this.logger = Logger.getLogger(description.getPrefix() != null ? description.getPrefix() : description.getName()); + this.pluginMeta = configuration; // Paper ++ this.logger = Logger.getLogger(description.getPrefix() != null ? description.getPrefix() : description.getName()); // Paper - Handle plugin prefix in implementation } /** diff --git a/patches/api/List-all-missing-hard-depends-not-just-first.patch b/patches/api/List-all-missing-hard-depends-not-just-first.patch deleted file mode 100644 index 622b6742e0..0000000000 --- a/patches/api/List-all-missing-hard-depends-not-just-first.patch +++ /dev/null @@ -1,91 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic <jake.m.potrebic@gmail.com> -Date: Tue, 18 May 2021 10:38:10 -0700 -Subject: [PATCH] List all missing hard depends not just first - - -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - - if (dependencies.containsKey(plugin)) { - Iterator<String> dependencyIterator = dependencies.get(plugin).iterator(); -+ final Set<String> missingHardDependencies = new HashSet<>(dependencies.get(plugin).size()); // Paper - list all missing hard depends - - while (dependencyIterator.hasNext()) { - String dependency = dependencyIterator.next(); -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - - // We have a dependency not found - } else if (!plugins.containsKey(dependency) && !pluginsProvided.containsKey(dependency)) { -+ // Paper start -+ missingHardDependencies.add(dependency); -+ } -+ } -+ if (!missingHardDependencies.isEmpty()) { -+ // Paper end - missingDependency = false; - pluginIterator.remove(); - softDependencies.remove(plugin); -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - server.getLogger().log( - Level.SEVERE, - "Could not load '" + entry.getValue().getPath() + "' in folder '" + entry.getValue().getParentFile().getPath() + "'", // Paper -- new UnknownDependencyException("Unknown dependency " + dependency + ". Please download and install " + dependency + " to run this plugin.")); -- break; -- } -+ new UnknownDependencyException(missingHardDependencies, plugin)); // Paper - } - - if (dependencies.containsKey(plugin) && dependencies.get(plugin).isEmpty()) { -diff --git a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java -+++ b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java -@@ -0,0 +0,0 @@ public class UnknownDependencyException extends RuntimeException { - super(message); - } - -+ // Paper start -+ /** -+ * Create a new {@link UnknownDependencyException} with a message informing -+ * about which dependencies are missing for what plugin. -+ * -+ * @param missingDependencies missing dependencies -+ * @param pluginName plugin which is missing said dependencies -+ */ -+ public UnknownDependencyException(final @org.jetbrains.annotations.NotNull java.util.Collection<String> missingDependencies, final @org.jetbrains.annotations.NotNull String pluginName) { -+ this("Unknown/missing dependency plugins: [" + String.join(", ", missingDependencies) + "]. Please download and install these plugins to run '" + pluginName + "'."); -+ } -+ // Paper end -+ - /** - * Constructs a new UnknownDependencyException based on the given - * Exception -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - )); - } - -+ Set<String> missingHardDependencies = new HashSet<>(description.getDepend().size()); // Paper - list all missing hard depends - for (final String pluginName : description.getDepend()) { - Plugin current = server.getPluginManager().getPlugin(pluginName); - - if (current == null) { -- throw new UnknownDependencyException("Unknown dependency " + pluginName + ". Please download and install " + pluginName + " to run this plugin."); -+ missingHardDependencies.add(pluginName); // Paper - list all missing hard depends - } - } -+ // Paper start - list all missing hard depends -+ if (!missingHardDependencies.isEmpty()) { -+ throw new UnknownDependencyException(missingHardDependencies, description.getFullName()); -+ } -+ // Paper end - - server.getUnsafe().checkSupported(description); - diff --git a/patches/api/Make-JavaPluginLoader-thread-safe.patch b/patches/api/Make-JavaPluginLoader-thread-safe.patch deleted file mode 100644 index 33c26d2809..0000000000 --- a/patches/api/Make-JavaPluginLoader-thread-safe.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Trigary <trigary0@gmail.com> -Date: Wed, 15 Apr 2020 01:24:55 -0400 -Subject: [PATCH] Make JavaPluginLoader thread-safe - - -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ import org.yaml.snakeyaml.error.YAMLException; - public final class JavaPluginLoader implements PluginLoader { - final Server server; - private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")}; -+ private final Map<String, java.util.concurrent.locks.ReentrantReadWriteLock> classLoadLock = new java.util.HashMap<String, java.util.concurrent.locks.ReentrantReadWriteLock>(); // Paper -+ private final Map<String, Integer> classLoadLockCount = new java.util.HashMap<String, Integer>(); // Paper - private final List<PluginClassLoader> loaders = new CopyOnWriteArrayList<PluginClassLoader>(); - private final LibraryLoader libraryLoader; - -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - - @Nullable - Class<?> getClassByName(final String name, boolean resolve, PluginDescriptionFile description) { -+ // Paper start - make MT safe -+ java.util.concurrent.locks.ReentrantReadWriteLock lock; -+ synchronized (classLoadLock) { -+ lock = classLoadLock.computeIfAbsent(name, (x) -> new java.util.concurrent.locks.ReentrantReadWriteLock()); -+ classLoadLockCount.compute(name, (x, prev) -> prev != null ? prev + 1 : 1); -+ } -+ lock.writeLock().lock();try { -+ // Paper end - for (PluginClassLoader loader : loaders) { - try { - return loader.loadClass0(name, resolve, false, ((SimplePluginManager) server.getPluginManager()).isTransitiveDepend(description, loader.plugin.getDescription())); - } catch (ClassNotFoundException cnfe) { - } - } -+ // Paper start - make MT safe -+ } finally { -+ synchronized (classLoadLock) { -+ lock.writeLock().unlock(); -+ if (classLoadLockCount.get(name) == 1) { -+ classLoadLock.remove(name); -+ classLoadLockCount.remove(name); -+ } else { -+ classLoadLockCount.compute(name, (x, prev) -> prev - 1); -+ } -+ } -+ } -+ // Paper end - return null; - } - diff --git a/patches/api/Make-plugins-list-alphabetical.patch b/patches/api/Make-plugins-list-alphabetical.patch deleted file mode 100644 index b714733ba0..0000000000 --- a/patches/api/Make-plugins-list-alphabetical.patch +++ /dev/null @@ -1,56 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BillyGalbreath <Blake.Galbreath@GMail.com> -Date: Mon, 31 Jul 2017 02:08:55 -0500 -Subject: [PATCH] Make /plugins list alphabetical - - -diff --git a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java -+++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java -@@ -0,0 +0,0 @@ package org.bukkit.command.defaults; - import java.util.Arrays; - import java.util.Collections; - import java.util.List; -+import java.util.Map; -+import java.util.TreeMap; -+ - import org.bukkit.Bukkit; - import org.bukkit.ChatColor; - import org.bukkit.command.CommandSender; -@@ -0,0 +0,0 @@ public class PluginsCommand extends BukkitCommand { - - @NotNull - private String getPluginList() { -- StringBuilder pluginList = new StringBuilder(); -- Plugin[] plugins = Bukkit.getPluginManager().getPlugins(); -+ // Paper start -+ TreeMap<String, Plugin> plugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); -+ -+ for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { -+ plugins.put(plugin.getDescription().getName(), plugin); -+ } - -- for (Plugin plugin : plugins) { -+ StringBuilder pluginList = new StringBuilder(); -+ for (Map.Entry<String, Plugin> entry : plugins.entrySet()) { - if (pluginList.length() > 0) { - pluginList.append(ChatColor.WHITE); - pluginList.append(", "); - } - -+ Plugin plugin = entry.getValue(); -+ - pluginList.append(plugin.isEnabled() ? ChatColor.GREEN : ChatColor.RED); - pluginList.append(plugin.getDescription().getName()); - -@@ -0,0 +0,0 @@ public class PluginsCommand extends BukkitCommand { - } - } - -- return "(" + plugins.length + "): " + pluginList.toString(); -+ return "(" + plugins.size() + "): " + pluginList.toString(); -+ // Paper end - } -+ - } diff --git a/patches/api/Paper-Plugins.patch b/patches/api/Paper-Plugins.patch new file mode 100644 index 0000000000..33542a2054 --- /dev/null +++ b/patches/api/Paper-Plugins.patch @@ -0,0 +1,2494 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 6 Jul 2022 23:00:36 -0400 +Subject: [PATCH] Paper Plugins + + +diff --git a/build.gradle.kts b/build.gradle.kts +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/build.gradle.kts ++++ b/build.gradle.kts +@@ -0,0 +0,0 @@ dependencies { + implementation("org.ow2.asm:asm-commons:9.2") + // Paper end + +- compileOnly("org.apache.maven:maven-resolver-provider:3.8.5") ++ api("org.apache.maven:maven-resolver-provider:3.8.5") // Paper, expose + compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.7.3") + compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.7.3") + compileOnly("com.google.code.findbugs:jsr305:1.3.9") // Paper +diff --git a/src/main/java/io/papermc/paper/plugin/PermissionManager.java b/src/main/java/io/papermc/paper/plugin/PermissionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/PermissionManager.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import org.bukkit.permissions.Permissible; ++import org.bukkit.permissions.Permission; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.List; ++import java.util.Set; ++ ++/** ++ * A permission manager implementation to keep backwards compatibility partially alive with existing plugins that used ++ * the bukkit one before. ++ */ ++@ApiStatus.Experimental ++public interface PermissionManager { ++ ++ /** ++ * Gets a {@link Permission} from its fully qualified name ++ * ++ * @param name Name of the permission ++ * @return Permission, or null if none ++ */ ++ @Nullable ++ Permission getPermission(@NotNull String name); ++ ++ /** ++ * Adds a {@link Permission} to this plugin manager. ++ * <p> ++ * If a permission is already defined with the given name of the new ++ * permission, an exception will be thrown. ++ * ++ * @param perm Permission to add ++ * @throws IllegalArgumentException Thrown when a permission with the same ++ * name already exists ++ */ ++ void addPermission(@NotNull Permission perm); ++ ++ /** ++ * Removes a {@link Permission} registration from this plugin manager. ++ * <p> ++ * If the specified permission does not exist in this plugin manager, ++ * nothing will happen. ++ * <p> ++ * Removing a permission registration will <b>not</b> remove the ++ * permission from any {@link Permissible}s that have it. ++ * ++ * @param perm Permission to remove ++ */ ++ void removePermission(@NotNull Permission perm); ++ ++ /** ++ * Removes a {@link Permission} registration from this plugin manager. ++ * <p> ++ * If the specified permission does not exist in this plugin manager, ++ * nothing will happen. ++ * <p> ++ * Removing a permission registration will <b>not</b> remove the ++ * permission from any {@link Permissible}s that have it. ++ * ++ * @param name Permission to remove ++ */ ++ void removePermission(@NotNull String name); ++ ++ /** ++ * Gets the default permissions for the given op status ++ * ++ * @param op Which set of default permissions to get ++ * @return The default permissions ++ */ ++ @NotNull ++ Set<Permission> getDefaultPermissions(boolean op); ++ ++ /** ++ * Recalculates the defaults for the given {@link Permission}. ++ * <p> ++ * This will have no effect if the specified permission is not registered ++ * here. ++ * ++ * @param perm Permission to recalculate ++ */ ++ void recalculatePermissionDefaults(@NotNull Permission perm); ++ ++ /** ++ * Subscribes the given Permissible for information about the requested ++ * Permission, by name. ++ * <p> ++ * If the specified Permission changes in any form, the Permissible will ++ * be asked to recalculate. ++ * ++ * @param permission Permission to subscribe to ++ * @param permissible Permissible subscribing ++ */ ++ void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible); ++ ++ /** ++ * Unsubscribes the given Permissible for information about the requested ++ * Permission, by name. ++ * ++ * @param permission Permission to unsubscribe from ++ * @param permissible Permissible subscribing ++ */ ++ void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible); ++ ++ /** ++ * Gets a set containing all subscribed {@link Permissible}s to the given ++ * permission, by name ++ * ++ * @param permission Permission to query for ++ * @return Set containing all subscribed permissions ++ */ ++ @NotNull ++ Set<Permissible> getPermissionSubscriptions(@NotNull String permission); ++ ++ /** ++ * Subscribes to the given Default permissions by operator status ++ * <p> ++ * If the specified defaults change in any form, the Permissible will be ++ * asked to recalculate. ++ * ++ * @param op Default list to subscribe to ++ * @param permissible Permissible subscribing ++ */ ++ void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible); ++ ++ /** ++ * Unsubscribes from the given Default permissions by operator status ++ * ++ * @param op Default list to unsubscribe from ++ * @param permissible Permissible subscribing ++ */ ++ void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible); ++ ++ /** ++ * Gets a set containing all subscribed {@link Permissible}s to the given ++ * default list, by op status ++ * ++ * @param op Default list to query for ++ * @return Set containing all subscribed permissions ++ */ ++ @NotNull ++ Set<Permissible> getDefaultPermSubscriptions(boolean op); ++ ++ /** ++ * Gets a set of all registered permissions. ++ * <p> ++ * This set is a copy and will not be modified live. ++ * ++ * @return Set containing all current registered permissions ++ */ ++ @NotNull ++ Set<Permission> getPermissions(); ++ ++ /** ++ * Adds a list of permissions. ++ * <p> ++ * This is meant as an optimization for adding multiple permissions without recalculating each permission. ++ * ++ * @param perm permission ++ */ ++ void addPermissions(@NotNull List<Permission> perm); ++ ++ /** ++ * Clears the current registered permissinos. ++ * <p> ++ * This is used for reloading. ++ */ ++ void clearPermissions(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrap.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrap.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrap.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.bootstrap; ++ ++import io.papermc.paper.plugin.provider.util.ProviderUtil; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A plugin boostrap is meant for loading certain parts of the plugin before the server is loaded. ++ * <p> ++ * Plugin bootstrapping allows values to be initialized in certain parts of the server that might not be allowed ++ * when the server is running. ++ * <p> ++ * Your bootstrap class will be on the same classloader as your JavaPlugin. ++ * <p> ++ * <b>All calls to Bukkit may throw a NullPointerExceptions or return null unexpectedly. You should only call api methods that are explicitly documented to work in the bootstrapper</b> ++ */ ++@ApiStatus.OverrideOnly ++@ApiStatus.Experimental ++public interface PluginBootstrap { ++ ++ /** ++ * Called by the server, allowing you to bootstrap the plugin with a context that provides things like a logger and your shared plugin configuration file. ++ * ++ * @param context the server provided context ++ */ ++ void bootstrap(@NotNull PluginProviderContext context); ++ ++ /** ++ * Called by the server to instantiate your main class. ++ * Plugins may override this logic to define custom creation logic for said instance, like passing addition ++ * constructor arguments. ++ * ++ * @param context the server created bootstrap object ++ * @return the server requested instance of the plugins main class. ++ */ ++ @NotNull ++ default JavaPlugin createPlugin(@NotNull PluginProviderContext context) { ++ return ProviderUtil.loadClass(context.getConfiguration().getMainClass(), JavaPlugin.class, this.getClass().getClassLoader()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContext.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContext.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContext.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.bootstrap; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Path; ++import java.util.logging.Logger; ++ ++/** ++ * Represents the context provided to a {@link PluginBootstrap} during both the bootstrapping and plugin ++ * instanciation logic. ++ * A boostrap context may be used to access data or logic usually provided to {@link org.bukkit.plugin.Plugin} instances ++ * like the plugin's configuration or logger during the plugins bootstrap. ++ */ ++@ApiStatus.NonExtendable ++@ApiStatus.Experimental ++public interface PluginProviderContext { ++ ++ /** ++ * Provides the plugin's configuration. ++ * ++ * @return the plugin's configuration ++ */ ++ @NotNull ++ PluginMeta getConfiguration(); ++ ++ /** ++ * Provides the path to the data directory of the plugin. ++ * ++ * @return the previously described path ++ */ ++ @NotNull ++ Path getDataDirectory(); ++ ++ /** ++ * Provides the logger used for this plugin. ++ * ++ * @return the logger instance ++ */ ++ @NotNull ++ Logger getLogger(); ++ ++ /** ++ * Provides the SLF4J logger assigned to this plugin. ++ * ++ * @return SLF4J logger ++ */ ++ @NotNull ++ default org.slf4j.Logger getSLF4JLogger() { ++ return org.slf4j.LoggerFactory.getLogger(this.getLogger().getName()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/configuration/PluginMeta.java b/src/main/java/io/papermc/paper/plugin/configuration/PluginMeta.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/configuration/PluginMeta.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.configuration; ++ ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.bukkit.plugin.PluginLoadOrder; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.List; ++ ++/** ++ * This class acts as an abstraction for a plugin configuration. ++ */ ++@ApiStatus.NonExtendable ++@ApiStatus.Experimental // Subject to change! ++public interface PluginMeta { ++ ++ /** ++ * Provides the name of the plugin. This name uniquely identifies the plugin amongst all loaded plugins on the ++ * server. ++ * <ul> ++ * <li>Will only contain alphanumeric characters, underscores, hyphens, ++ * and periods: [a-zA-Z0-9_\-\.]. ++ * <li>Typically used for identifying the plugin data folder. ++ * <li>The name also acts as the token referenced in {@link #getPluginDependencies()}, ++ * {@link #getPluginSoftDependencies()}, and {@link #getLoadBeforePlugins()}. ++ * </ul> ++ * <p> ++ * In the plugin.yml, this entry is named <code>name</code>. ++ * <p> ++ * Example:<blockquote><pre>name: MyPlugin</pre></blockquote> ++ * ++ * @return the name of the plugin ++ */ ++ @NotNull ++ String getName(); ++ ++ /** ++ * Returns the display name of the plugin, including the version. ++ * ++ * @return a descriptive name of the plugin and respective version ++ */ ++ @NotNull ++ default String getDisplayName() { ++ return this.getName() + " v" + this.getVersion(); ++ } ++ ++ /** ++ * Provides the fully qualified class name of the main class for the plugin. ++ * A subtype of {@link JavaPlugin} is expected at this location. ++ * ++ * @return the fully qualified class name of the plugin's main class. ++ */ ++ @NotNull ++ String getMainClass(); ++ ++ /** ++ * Returns the phase of the server startup logic that the plugin should be loaded. ++ * ++ * @return the plugin load order ++ * @see PluginLoadOrder for further details regards the available load orders. ++ */ ++ @NotNull ++ PluginLoadOrder getLoadOrder(); ++ ++ /** ++ * Provides the version of this plugin as defined by the plugin. ++ * There is no inherit format defined/enforced for the version of a plugin, however a common approach ++ * might be schematic versioning. ++ * ++ * @return the string representation of the plugin's version ++ */ ++ @NotNull ++ String getVersion(); ++ ++ /** ++ * Provides the prefix that should be used for the plugin logger. ++ * The logger prefix allows plugins to overwrite the usual default of the logger prefix, which is the name of the ++ * plugin. ++ * ++ * @return the specific overwrite of the logger prefix as defined by the plugin. If the plugin did not define a ++ * custom logger prefix, this method will return null ++ */ ++ @Nullable ++ String getLoggerPrefix(); ++ ++ /** ++ * Provides a list of dependencies that are required for this plugin to load. ++ * The list holds the unique identifiers, following the constraints laid out in {@link #getName()}, of the ++ * dependencies. ++ * <p> ++ * If any of the dependencies defined by this list are not installed on the server, this plugin will fail to load. ++ * ++ * @return an immutable list of required dependency names ++ */ ++ @NotNull ++ List<String> getPluginDependencies(); ++ ++ /** ++ * Provides a list of dependencies that are used but not required by this plugin. ++ * The list holds the unique identifiers, following the constraints laid out in {@link #getName()}, of the soft ++ * dependencies. ++ * <p> ++ * If these dependencies are installed on the server, they will be loaded first and supplied as dependencies to this ++ * plugin, however the plugin will load even if these dependencies are not installed. ++ * ++ * @return immutable list of soft dependencies ++ */ ++ @NotNull ++ List<String> getPluginSoftDependencies(); ++ ++ /** ++ * Provides a list of plugins that should be loaded before this plugin is loaded. ++ * The list holds the unique identifiers, following the constraints laid out in {@link #getName()}, of the ++ * plugins that should be loaded before the plugin described by this plugin meta. ++ * <p> ++ * The plugins referenced in the list provided by this method are not considered dependencies of this plugin and ++ * are hence not available to the plugin at runtime. They merely load before this plugin. ++ * ++ * @return immutable list of plugins to load before this plugin ++ */ ++ @NotNull ++ List<String> getLoadBeforePlugins(); ++ ++ /** ++ * Returns the list of plugins/dependencies that this plugin provides. ++ * The list holds the unique identifiers, following the constraints laid out in {@link #getName()}, for each plugin ++ * it provides the expected classes for. ++ * ++ * @return immutable list of provided plugins/dependencies ++ */ ++ @NotNull ++ List<String> getProvidedPlugins(); ++ ++ /** ++ * Provides the list of authors that are credited with creating this plugin. ++ * The author names are in no particular format. ++ * ++ * @return an immutable list of the plugin's authors ++ */ ++ @NotNull ++ List<String> getAuthors(); ++ ++ /** ++ * Provides a list of contributors that contributed to the plugin but are not considered authors. ++ * The names of the contributors are in no particular format. ++ * ++ * @return an immutable list of the plugin's contributors ++ */ ++ @NotNull ++ List<String> getContributors(); ++ ++ /** ++ * Gives a human-friendly description of the functionality the plugin ++ * provides. ++ * ++ * @return description or null if the plugin did not define a human readable description. ++ */ ++ @Nullable ++ String getDescription(); ++ ++ /** ++ * Provides the website for the plugin or the plugin's author. ++ * The defined string value is <b>not guaranteed</b> to be in the form of a url. ++ * ++ * @return a string representation of the website that serves as the main hub for this plugin/its author. ++ */ ++ @Nullable ++ String getWebsite(); ++ ++ /** ++ * Provides the list of permissions that are defined via the plugin meta instance. ++ * ++ * @return an immutable list of permissions ++ */ ++ // TODO: Do we even want this? Why not just use the bootstrapper ++ @NotNull ++ List<Permission> getPermissions(); ++ ++ /** ++ * Provides the default values that apply to the permissions defined in this plugin meta. ++ * ++ * @return the bukkit permission default container. ++ * @see #getPermissions() ++ */ ++ // TODO: Do we even want this? Why not just use the bootstrapper ++ @NotNull ++ PermissionDefault getPermissionDefault(); ++ ++ /** ++ * Gets the api version that this plugin supports. ++ * Nullable if this version is not specified, and should be ++ * considered legacy (spigot plugins only) ++ * ++ * @return the version string made up of the major and minor version (e.g. 1.18 or 1.19). Minor versions like 1.18.2 ++ * are unified to their major release version (in this example 1.18) ++ */ ++ @Nullable ++ String getAPIVersion(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/configuration/package-info.java b/src/main/java/io/papermc/paper/plugin/configuration/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/configuration/package-info.java +@@ -0,0 +0,0 @@ ++/** ++ * The paper configuration package contains the new java representation of a plugins configuration file. ++ * While most values are described in detail on {@link io.papermc.paper.plugin.configuration.PluginMeta}, a full ++ * entry on the paper contains a full and extensive example of possible configurations of the paper-plugin.yml. ++ * @see <a href="https://docs.papermc.io/paper">Extensive documentation and examples of the paper-plugin.yml</a> ++ * <!--TODO update the documentation link once documentation for this exists and is deployed--> ++ */ ++package io.papermc.paper.plugin.configuration; +diff --git a/src/main/java/io/papermc/paper/plugin/loader/PluginClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PluginClasspathBuilder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/PluginClasspathBuilder.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader; ++ ++import io.papermc.paper.plugin.bootstrap.PluginProviderContext; ++import io.papermc.paper.plugin.loader.library.ClassPathLibrary; ++import io.papermc.paper.plugin.loader.library.LibraryStore; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A mutable builder that may be used to collect and register all {@link ClassPathLibrary} instances a ++ * {@link PluginLoader} aims to provide to its plugin at runtime. ++ */ ++@ApiStatus.NonExtendable ++@ApiStatus.Experimental ++public interface PluginClasspathBuilder { ++ ++ /** ++ * Adds a new classpath library to this classpath builder. ++ * <p> ++ * As a builder, this method does not invoke {@link ClassPathLibrary#register(LibraryStore)} and ++ * may hence be run without invoking potential IO performed by a {@link ClassPathLibrary} during resolution. ++ * <p> ++ * The paper api provides pre implemented {@link ClassPathLibrary} types that allow easy inclusion of existing ++ * libraries on disk or on remote maven repositories. ++ * ++ * @param classPathLibrary the library instance to add to this builder ++ * @return self ++ * @see io.papermc.paper.plugin.loader.library.impl.JarLibrary ++ * @see io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver ++ */ ++ @NotNull ++ @Contract("_ -> this") ++ PluginClasspathBuilder addLibrary(@NotNull ClassPathLibrary classPathLibrary); ++ ++ @NotNull ++ PluginProviderContext getContext(); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/PluginLoader.java b/src/main/java/io/papermc/paper/plugin/loader/PluginLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/PluginLoader.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader; ++ ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A plugin loader is responsible for creating certain aspects of a plugin before it is created. ++ * <p> ++ * The goal of the plugin loader is the creation of an expected/dynamic environment for the plugin to load into. ++ * This, as of right now, only applies to creating the expected classpath for the plugin, e.g. supplying external ++ * libraries to the plugin. ++ * <p> ++ * It should be noted that this class will be called from a different classloader, this will cause any static values ++ * set in this class/any other classes loaded not to persist when the plugin loads. ++ */ ++@ApiStatus.OverrideOnly ++@ApiStatus.Experimental ++public interface PluginLoader { ++ ++ /** ++ * Called by the server to allows plugins to configure the runtime classpath that the plugin is run on. ++ * This allows plugin loaders to configure dependencies for the plugin where jars can be downloaded or ++ * provided during runtime. ++ * ++ * @param classpathBuilder a mutable classpath builder that may be used to register custom runtime dependencies ++ * for the plugin the loader was registered for. ++ */ ++ void classloader(@NotNull PluginClasspathBuilder classpathBuilder); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/ClassPathLibrary.java b/src/main/java/io/papermc/paper/plugin/loader/library/ClassPathLibrary.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/library/ClassPathLibrary.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader.library; ++ ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * The classpath library interface represents libraries that are capable of registering themselves via ++ * {@link #register(LibraryStore)} on any given {@link LibraryStore}. ++ */ ++public interface ClassPathLibrary { ++ ++ /** ++ * Called to register the library this class path library represents into the passed library store. ++ * This method may either be implemented by the plugins themselves if they need complex logic, or existing ++ * API exposed implementations of this interface may be used. ++ * ++ * @param store the library store instance to register this library into ++ * @throws LibraryLoadingException if library loading failed for this classpath library ++ */ ++ void register(@NotNull LibraryStore store) throws LibraryLoadingException; ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/LibraryLoadingException.java b/src/main/java/io/papermc/paper/plugin/loader/library/LibraryLoadingException.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/library/LibraryLoadingException.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader.library; ++ ++/** ++ * Indicates that an exception has occured while loading a library. ++ */ ++public class LibraryLoadingException extends RuntimeException { ++ ++ public LibraryLoadingException(String s) { ++ super(s); ++ } ++ ++ public LibraryLoadingException(String s, Exception e) { ++ super(s, e); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/LibraryStore.java b/src/main/java/io/papermc/paper/plugin/loader/library/LibraryStore.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/library/LibraryStore.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader.library; ++ ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Path; ++ ++/** ++ * Represents a storage that stores library jars. ++ * <p> ++ * The library store api allows plugins to register specific dependencies into their runtime classloader when their ++ * {@link io.papermc.paper.plugin.loader.PluginLoader} is processed. ++ * ++ * @see io.papermc.paper.plugin.loader.PluginLoader ++ */ ++@ApiStatus.Internal ++public interface LibraryStore { ++ ++ /** ++ * Adds the provided library path to this library store. ++ * ++ * @param library path to the libraries jar file on the disk ++ */ ++ void addLibrary(@NotNull Path library); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/impl/JarLibrary.java b/src/main/java/io/papermc/paper/plugin/loader/library/impl/JarLibrary.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/library/impl/JarLibrary.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader.library.impl; ++ ++import io.papermc.paper.plugin.loader.library.ClassPathLibrary; ++import io.papermc.paper.plugin.loader.library.LibraryLoadingException; ++import io.papermc.paper.plugin.loader.library.LibraryStore; ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Files; ++import java.nio.file.Path; ++ ++/** ++ * A simple jar library implementation of the {@link ClassPathLibrary} that allows {@link io.papermc.paper.plugin.loader.PluginLoader}s to ++ * append a jar stored on the local file system into their runtime classloader. ++ * <p> ++ * An example creation of the jar library type may look like this: ++ * <pre>{@code ++ * final JarLibrary customLibrary = new JarLibrary(Path.of("libs/custom-library-1.24.jar")); ++ * }</pre> ++ * resulting in a jar library that provides the jar at {@code libs/custom-library-1.24.jar} to the plugins classloader ++ * at runtime. ++ * <p> ++ * The jar library implementation will error if file exists at the specified path. ++ */ ++public class JarLibrary implements ClassPathLibrary { ++ ++ private final Path path; ++ ++ /** ++ * Creates a new jar library that references the jar file found at the provided path. ++ * ++ * @param path the path, relative to the JVMs start directory. ++ */ ++ public JarLibrary(@NotNull Path path) { ++ this.path = path; ++ } ++ ++ @Override ++ public void register(@NotNull LibraryStore store) throws LibraryLoadingException { ++ if (Files.notExists(this.path)) { ++ throw new LibraryLoadingException("Could not find library at " + this.path); ++ } ++ ++ store.addLibrary(this.path); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java b/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader.library.impl; ++ ++import io.papermc.paper.plugin.loader.library.ClassPathLibrary; ++import io.papermc.paper.plugin.loader.library.LibraryLoadingException; ++import io.papermc.paper.plugin.loader.library.LibraryStore; ++import org.apache.maven.repository.internal.MavenRepositorySystemUtils; ++import org.eclipse.aether.DefaultRepositorySystemSession; ++import org.eclipse.aether.RepositorySystem; ++import org.eclipse.aether.collection.CollectRequest; ++import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory; ++import org.eclipse.aether.graph.Dependency; ++import org.eclipse.aether.impl.DefaultServiceLocator; ++import org.eclipse.aether.repository.LocalRepository; ++import org.eclipse.aether.repository.RemoteRepository; ++import org.eclipse.aether.repository.RepositoryPolicy; ++import org.eclipse.aether.resolution.ArtifactResult; ++import org.eclipse.aether.resolution.DependencyRequest; ++import org.eclipse.aether.resolution.DependencyResolutionException; ++import org.eclipse.aether.resolution.DependencyResult; ++import org.eclipse.aether.spi.connector.RepositoryConnectorFactory; ++import org.eclipse.aether.spi.connector.transport.TransporterFactory; ++import org.eclipse.aether.transfer.AbstractTransferListener; ++import org.eclipse.aether.transfer.TransferCancelledException; ++import org.eclipse.aether.transfer.TransferEvent; ++import org.eclipse.aether.transport.http.HttpTransporterFactory; ++import org.jetbrains.annotations.NotNull; ++ ++import java.io.File; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++/** ++ * The maven library resolver acts as a resolver for yet to be resolved jar libraries that may be pulled from a ++ * remote maven repository. ++ * <p> ++ * Plugins may create and configure a {@link MavenLibraryResolver} by creating a new one and registering both ++ * a dependency artifact that should be resolved to a library at runtime and the repository it is found in. ++ * An example of this would be the inclusion of the jooq library for typesafe SQL queries: ++ * <pre>{@code ++ * MavenLibraryResolver resolver = new MavenLibraryResolver(); ++ * resolver.addDependency(new Dependency(new DefaultArtifact("org.jooq:jooq:3.17.7"), null)); ++ * resolver.addRepository(new RemoteRepository.Builder( ++ * "central", "default", "https://repo1.maven.org/maven2/" ++ * ).build()); ++ * }</pre> ++ * ++ * Plugins may create and register a {@link MavenLibraryResolver} after configuring it. ++ */ ++public class MavenLibraryResolver implements ClassPathLibrary { ++ ++ private static final Logger logger = Logger.getLogger("MavenLibraryResolver"); ++ ++ private final RepositorySystem repository; ++ private final DefaultRepositorySystemSession session; ++ private final List<RemoteRepository> repositories = new ArrayList<>(); ++ private final List<Dependency> dependencies = new ArrayList<>(); ++ ++ /** ++ * Creates a new maven library resolver instance. ++ * <p> ++ * The created instance will use the servers {@code libraries} folder to cache fetched libraries in. ++ * Notably, the resolver is created without any repository, not even maven central. ++ * It is hence crucial that plugins which aim to use this api register all required repositories before ++ * submitting the {@link MavenLibraryResolver} to the {@link io.papermc.paper.plugin.loader.PluginClasspathBuilder}. ++ */ ++ public MavenLibraryResolver() { ++ DefaultServiceLocator locator = MavenRepositorySystemUtils.newServiceLocator(); ++ locator.addService(RepositoryConnectorFactory.class, BasicRepositoryConnectorFactory.class); ++ locator.addService(TransporterFactory.class, HttpTransporterFactory.class); ++ ++ this.repository = locator.getService(RepositorySystem.class); ++ this.session = MavenRepositorySystemUtils.newSession(); ++ ++ this.session.setChecksumPolicy(RepositoryPolicy.CHECKSUM_POLICY_FAIL); ++ this.session.setLocalRepositoryManager(this.repository.newLocalRepositoryManager(this.session, new LocalRepository("libraries"))); ++ this.session.setTransferListener(new AbstractTransferListener() { ++ @Override ++ public void transferInitiated(@NotNull TransferEvent event) throws TransferCancelledException { ++ logger.log(Level.INFO, "Downloading {0}", event.getResource().getRepositoryUrl() + event.getResource().getResourceName()); ++ } ++ }); ++ this.session.setReadOnly(); ++ } ++ ++ /** ++ * Adds the provided dependency to the library resolver. ++ * The artifact from the first valid repository matching the passed dependency will be chosen. ++ * ++ * @param dependency the definition of the dependency the maven library resolver should resolve when running ++ * @see MavenLibraryResolver#addRepository(RemoteRepository) ++ */ ++ public void addDependency(@NotNull Dependency dependency) { ++ this.dependencies.add(dependency); ++ } ++ ++ /** ++ * Adds the provided repository to the library resolver. ++ * The order in which these are added does matter, as dependency resolving will start at the first added ++ * repository. ++ * ++ * @param remoteRepository the configuration that defines the maven repository this library resolver should fetch ++ * dependencies from ++ */ ++ public void addRepository(@NotNull RemoteRepository remoteRepository) { ++ this.repositories.add(remoteRepository); ++ } ++ ++ /** ++ * Resolves the provided dependencies and adds them to the library store. ++ * ++ * @param store the library store the then resolved and downloaded dependencies are registered into ++ * @throws LibraryLoadingException if resolving a dependency failed ++ */ ++ @Override ++ public void register(@NotNull LibraryStore store) throws LibraryLoadingException { ++ List<RemoteRepository> repos = this.repository.newResolutionRepositories(this.session, this.repositories); ++ ++ DependencyResult result; ++ try { ++ result = this.repository.resolveDependencies(this.session, new DependencyRequest(new CollectRequest((Dependency) null, this.dependencies, repos), null)); ++ } catch (DependencyResolutionException ex) { ++ throw new LibraryLoadingException("Error resolving libraries", ex); ++ } ++ ++ for (ArtifactResult artifact : result.getArtifactResults()) { ++ File file = artifact.getArtifact().getFile(); ++ store.addLibrary(file.toPath()); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/classloader/ClassLoaderAccess.java b/src/main/java/io/papermc/paper/plugin/provider/classloader/ClassLoaderAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/ClassLoaderAccess.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.classloader; ++ ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * The class loader access interface is an <b>internal</b> representation of a class accesses' ability to see types ++ * from other {@link ConfiguredPluginClassLoader}. ++ * <p> ++ * An example of this would be a class loader access representing a plugin. The class loader access in that case would ++ * only return {@code true} on calls for {@link #canAccess(ConfiguredPluginClassLoader)} if the passed class loader ++ * is owned by a direct or transitive dependency of the plugin, preventing the plugin for accidentally discovering and ++ * using class types that are supplied by plugins/libraries the plugin did not actively define as a dependency. ++ */ ++@ApiStatus.Internal ++public interface ClassLoaderAccess { ++ ++ /** ++ * Evaluates if this class loader access is allowed to access types provided by the passed {@link ++ * ConfiguredPluginClassLoader}. ++ * <p> ++ * This interface method does not offer any further contracts on the interface level, as the logic to determine ++ * what class loaders this class loader access is allowed to retrieve types from depends heavily on the type of ++ * access. ++ * Legacy spigot types for example may access any class loader available on the server, while modern paper plugins ++ * are properly limited to their dependency tree. ++ * ++ * @param classLoader the class loader for which access should be evaluated ++ * @return a plain boolean flag, {@code true} indicating that this class loader access is allowed to access types ++ * from the passed configured plugin class loader, {@code false} indicating otherwise. ++ */ ++ boolean canAccess(ConfiguredPluginClassLoader classLoader); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/classloader/ConfiguredPluginClassLoader.java b/src/main/java/io/papermc/paper/plugin/provider/classloader/ConfiguredPluginClassLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/ConfiguredPluginClassLoader.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.classloader; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import java.io.Closeable; ++ ++/** ++ * The configured plugin class loader represents an <b>internal</b> abstraction over the classloaders used by the server ++ * to load and access a plugins classes during runtime. ++ * <p> ++ * It implements {@link Closeable} to define the ability to shutdown and close the classloader that implements this ++ * interface. ++ */ ++@ApiStatus.Internal ++public interface ConfiguredPluginClassLoader extends Closeable { ++ ++ /** ++ * Provides the configuration of the plugin that this plugin classloader provides type access to. ++ * ++ * @return the plugin meta instance, holding all meta information about the plugin instance. ++ */ ++ PluginMeta getConfiguration(); ++ ++ /** ++ * Attempts to load a class from this plugin class loader using the passed fully qualified name. ++ * This lookup logic can be configured through the following parameters to define how wide or how narrow the ++ * class lookup should be. ++ * ++ * @param name the fully qualified name of the class to load ++ * @param resolve whether the class should be resolved if needed or not ++ * @param checkGlobal whether this lookup should check transitive dependencies, including either the legacy spigot ++ * global class loader or the paper {@link PluginClassLoaderGroup} ++ * @param checkLibraries whether the defined libraries should be checked for the class or not ++ * @return the class found at the fully qualified class name passed under the passed restrictions ++ * @throws ClassNotFoundException if the class could not be found considering the passed restrictions ++ * @see ClassLoader#loadClass(String) ++ * @see Class#forName(String, boolean, ClassLoader) ++ */ ++ Class<?> loadClass(@NotNull String name, ++ boolean resolve, ++ boolean checkGlobal, ++ boolean checkLibraries) throws ClassNotFoundException; ++ ++ /** ++ * Initializes both this configured plugin class loader and the java plugin passed to link to each other. ++ * This logic is to be called exactly once when the initial setup between the class loader and the instantiated ++ * {@link JavaPlugin} is loaded. ++ * ++ * @param plugin the {@link JavaPlugin} that should be interlinked with this class loader. ++ */ ++ void init(JavaPlugin plugin); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorage.java b/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.classloader; ++ ++import org.bukkit.plugin.java.PluginClassLoader; ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * The plugin classloader storage is an <b>internal</b> type that is used to manage existing classloaders on the server. ++ * <p> ++ * The paper classloader storage is also responsible for storing added {@link ConfiguredPluginClassLoader}s into ++ * {@link PluginClassLoaderGroup}s, via {@link #registerOpenGroup(ConfiguredPluginClassLoader)}, ++ * {@link #registerSpigotGroup(PluginClassLoader)} and {@link ++ * #registerAccessBackedGroup(ConfiguredPluginClassLoader, ClassLoaderAccess)}. ++ * <p> ++ * Groups are differentiated into the global group or plugin owned groups. ++ * <ul> ++ * <li>The global group holds all registered class loaders and merely exists to maintain backwards compatibility with ++ * spigots legacy classloader handling.</li> ++ * <li>The plugin groups only contains the classloaders that each plugin has access to and hence serves to properly ++ * separates unrelated classloaders.</li> ++ * </ul> ++ */ ++@ApiStatus.Internal ++public interface PaperClassLoaderStorage { ++ ++ /** ++ * Access to the shared instance of the {@link PaperClassLoaderStorageAccess}. ++ * ++ * @return the singleton instance of the {@link PaperClassLoaderStorage} used throughout the server ++ */ ++ static PaperClassLoaderStorage instance() { ++ return PaperClassLoaderStorageAccess.INSTANCE; ++ } ++ ++ /** ++ * Registers a legacy spigot {@link PluginClassLoader} into the loader storage, creating a group wrapping ++ * the single plugin class loader with transitive access to the global group. ++ * ++ * @param pluginClassLoader the legacy spigot plugin class loader to register ++ * @return the group the plugin class loader was placed into ++ */ ++ PluginClassLoaderGroup registerSpigotGroup(PluginClassLoader pluginClassLoader); ++ ++ /** ++ * Registers a paper configured plugin classloader into a new open group, with full access to the global ++ * plugin class loader group. ++ * <p> ++ * This method hence allows the configured plugin class loader to access all other class loaders registered in this ++ * storage. ++ * ++ * @param classLoader the configured plugin class loader to register ++ * @return the group the plugin class loader was placed into ++ */ ++ PluginClassLoaderGroup registerOpenGroup(ConfiguredPluginClassLoader classLoader); ++ ++ /** ++ * Registers a paper configured classloader into a new, access backed group. ++ * The access backed classloader group, different from an open group, only has access to the classloaders ++ * the passed {@link ClassLoaderAccess} grants access to. ++ * ++ * @param classLoader the configured plugin class loader to register ++ * @param access the class loader access that defines what other classloaders the passed plugin class loader ++ * should be granted access to. ++ * @return the group the plugin class loader was placed into. ++ */ ++ PluginClassLoaderGroup registerAccessBackedGroup(ConfiguredPluginClassLoader classLoader, ClassLoaderAccess access); ++ ++ /** ++ * Unregisters a configured class loader from this storage. ++ * This removes the passed class loaders from any group it may have been a part of, including the global group. ++ * <p> ++ * Note: this method is <b>highly</b> discouraged from being used, as mutation of the classloaders at runtime ++ * is not encouraged ++ * ++ * @param configuredPluginClassLoader the class loader to remove from this storage. ++ */ ++ void unregisterClassloader(ConfiguredPluginClassLoader configuredPluginClassLoader); ++ ++ /** ++ * Registers a configured plugin class loader directly into the global group without adding it to ++ * any existing groups. ++ * <p> ++ * Note: this method unsafely injects the plugin classloader directly into the global group, which bypasses the ++ * group structure paper's plugin API introduced. This method should hence be used with caution. ++ * ++ * @param pluginLoader the configured plugin classloader instance that should be registered directly into the global ++ * group. ++ * @return a simple boolean flag, {@code true} if the classloader was registered or {@code false} if the classloader ++ * was already part of the global group. ++ */ ++ boolean registerUnsafePlugin(ConfiguredPluginClassLoader pluginLoader); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorageAccess.java b/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorageAccess.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorageAccess.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.classloader; ++ ++import net.kyori.adventure.util.Services; ++ ++/** ++ * The paper classloader storage access acts as the holder for the server provided implementation of the ++ * {@link PaperClassLoaderStorage} interface. ++ */ ++class PaperClassLoaderStorageAccess { ++ ++ /** ++ * The shared instance of the {@link PaperClassLoaderStorage}, supplied through the {@link java.util.ServiceLoader} ++ * by the server. ++ */ ++ static final PaperClassLoaderStorage INSTANCE = Services.service(PaperClassLoaderStorage.class).orElseThrow(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/classloader/PluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/provider/classloader/PluginClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/PluginClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.classloader; ++ ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * A plugin classloader group represents a group of classloaders that a plugins classloader may access. ++ * <p> ++ * An example of this would be a classloader group that holds all direct and transitive dependencies a plugin declared, ++ * allowing a plugins classloader to access classes included in these dependencies via this group. ++ */ ++@ApiStatus.Internal ++public interface PluginClassLoaderGroup { ++ ++ /** ++ * Attempts to find/load a class from this plugin class loader group using the passed fully qualified name ++ * in any of the classloaders that are part of this group. ++ * <p> ++ * The lookup order across the contained loaders is not defined on the API level and depends purely on the ++ * implementation. ++ * ++ * @param name the fully qualified name of the class to load ++ * @param resolve whether the class should be resolved if needed or not ++ * @param requester plugin classloader that is requesting the class from this loader group ++ * @return the class found at the fully qualified class name passed. If the class could not be found, {@code null} ++ * will be returned. ++ * @see ConfiguredPluginClassLoader#loadClass(String, boolean, boolean, boolean) ++ */ ++ @Nullable ++ Class<?> getClassByName(String name, boolean resolve, ConfiguredPluginClassLoader requester); ++ ++ /** ++ * Removes a configured plugin classloader from this class loader group. ++ * If the classloader is not currently in the list, this method will simply do nothing. ++ * ++ * @param configuredPluginClassLoader the plugin classloader to remove from the group ++ */ ++ @Contract(mutates = "this") ++ void remove(ConfiguredPluginClassLoader configuredPluginClassLoader); ++ ++ /** ++ * Adds the passed plugin classloader to this group, allowing this group to use it during ++ * {@link #getClassByName(String, boolean, ConfiguredPluginClassLoader)} lookups. ++ * <p> ++ * This method does <b>not</b> query the {@link ClassLoaderAccess} (exposed via {@link #getAccess()}) to ensure ++ * if this group has access to the class loader passed. ++ * ++ * @param configuredPluginClassLoader the plugin classloader to add to this group. ++ */ ++ @Contract(mutates = "this") ++ void add(ConfiguredPluginClassLoader configuredPluginClassLoader); ++ ++ /** ++ * Provides the class loader access that guards and defines the content of this classloader group. ++ * While not guaranteed contractually (see {@link #add(ConfiguredPluginClassLoader)}), the access generally is ++ * responsible for defining which {@link ConfiguredPluginClassLoader}s should be part of this group and which ones ++ * should not. ++ * ++ * @return the classloader access governing which classloaders should be part of this group and which ones should ++ * not. ++ */ ++ ClassLoaderAccess getAccess(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/entrypoint/DependencyContext.java b/src/main/java/io/papermc/paper/plugin/provider/entrypoint/DependencyContext.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/entrypoint/DependencyContext.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.entrypoint; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * A dependency context is a read-only abstraction of a type/concept that can resolve dependencies between plugins. ++ * <p> ++ * This may for example be the server wide plugin manager itself, capable of validating if a dependency exists between ++ * two {@link PluginMeta} instances, however the implementation is not limited to such a concrete use-case. ++ */ ++@ApiStatus.Internal ++public interface DependencyContext { ++ ++ /** ++ * Computes if the passed {@link PluginMeta} defined the passed dependency as a transitive dependency. ++ * A transitive dependency, as implied by its name, may not have been configured directly by the passed plugin ++ * but could also simply be a dependency of a dependency. ++ * <p> ++ * A simple example of this method would be ++ * <pre>{@code ++ * dependencyContext.isTransitiveDependency(pluginMetaA, pluginMetaC); ++ * }</pre> ++ * which would return {@code true} if {@code pluginMetaA} directly or indirectly depends on {@code pluginMetaC}. ++ * ++ * @param plugin the plugin meta this computation should consider the requester of the dependency status for the ++ * passed potential dependency. ++ * @param depend the potential transitive dependency of the {@code plugin} parameter. ++ * @return a simple boolean flag indicating if {@code plugin} considers {@code depend} as a transitive dependency. ++ */ ++ boolean isTransitiveDependency(@NotNull PluginMeta plugin, @NotNull PluginMeta depend); ++ ++ /** ++ * Computes if this dependency context is aware of a dependency that provides/matches the passed identifier. ++ * <p> ++ * A dependency in this methods context is any dependable artefact. It does not matter if anything actually depends ++ * on said artefact, its mere existence as a potential dependency is enough for this method to consider it a ++ * dependency. If this dependency context is hence aware of an artefact with the matching identifier, this ++ * method returns {@code true}. ++ * ++ * @param pluginIdentifier the unique identifier of the dependency with which to probe this dependency context. ++ * @return a plain boolean flag indicating if this dependency context is aware of a potential dependency with the ++ * passed identifier. ++ */ ++ boolean hasDependency(@NotNull String pluginIdentifier); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/util/DummyBukkitPluginLoader.java b/src/main/java/io/papermc/paper/plugin/provider/util/DummyBukkitPluginLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/util/DummyBukkitPluginLoader.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.util; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.event.Event; ++import org.bukkit.event.Listener; ++import org.bukkit.plugin.InvalidDescriptionException; ++import org.bukkit.plugin.InvalidPluginException; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.PluginLoader; ++import org.bukkit.plugin.RegisteredListener; ++import org.bukkit.plugin.UnknownDependencyException; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import java.io.File; ++import java.util.Map; ++import java.util.Set; ++import java.util.regex.Pattern; ++ ++/** ++ * A purely internal type that implements the now deprecated {@link PluginLoader} after the implementation ++ * of papers new plugin system. ++ * ++ * @param plugin the loaded plugin that should be wrapped by this NOOP implementation ++ */ ++@ApiStatus.Internal ++public record DummyBukkitPluginLoader(Plugin plugin) implements PluginLoader { ++ ++ ++ @Override ++ public @NotNull Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull PluginDescriptionFile getPluginDescription(@NotNull File file) throws InvalidDescriptionException { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull Pattern[] getPluginFileFilters() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public @NotNull Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(@NotNull Listener listener, @NotNull Plugin plugin) { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public void enablePlugin(@NotNull Plugin plugin) { ++ Bukkit.getPluginManager().enablePlugin(plugin); ++ } ++ ++ @Override ++ public void disablePlugin(@NotNull Plugin plugin) { ++ Bukkit.getPluginManager().disablePlugin(plugin); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java b/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.util; ++ ++import com.destroystokyo.paper.util.SneakyThrow; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * An <b>internal</b> utility type that holds logic for loading a provider-like type from a classloaders. ++ * Provides, at least in the context of this utility, define themselves as implementations of a specific parent ++ * interface/type, e.g. {@link org.bukkit.plugin.java.JavaPlugin} and implement a no-args constructor. ++ */ ++@ApiStatus.Internal ++public class ProviderUtil { ++ ++ /** ++ * Loads the class found at the provided fully qualified class name from the passed classloader, creates a new ++ * instance of it using the no-args constructor, that should exist as per this method contract, and casts it to the ++ * provided parent type. ++ * ++ * @param clazz the fully qualified name of the class to load ++ * @param classType the parent type that the created object found at the {@code clazz} name should be cast to ++ * @param loader the loader from which the class should be loaded ++ * @param <T> the generic type of the parent class the created object will be cast to ++ * @return the object instantiated from the class found at the provided FQN, cast to the parent type ++ */ ++ @NotNull ++ public static <T> T loadClass(@NotNull String clazz, @NotNull Class<T> classType, @NotNull ClassLoader loader) { ++ return loadClass(clazz, classType, loader, null); ++ } ++ ++ /** ++ * Loads the class found at the provided fully qualified class name from the passed classloader, creates a new ++ * instance of it using the no-args constructor, that should exist as per this method contract, and casts it to the ++ * provided parent type. ++ * ++ * @param clazz the fully qualified name of the class to load ++ * @param classType the parent type that the created object found at the {@code clazz} name should be cast to ++ * @param loader the loader from which the class should be loaded ++ * @param onError a runnable that is executed before any unknown exception is raised through a sneaky throw. ++ * @param <T> the generic type of the parent class the created object will be cast to ++ * @return the object instantiated from the class found at the provided fully qualified class name, cast to the ++ * parent type ++ */ ++ @NotNull ++ public static <T> T loadClass(@NotNull String clazz, @NotNull Class<T> classType, @NotNull ClassLoader loader, @Nullable Runnable onError) { ++ try { ++ T clazzInstance; ++ ++ try { ++ Class<?> jarClass = Class.forName(clazz, true, loader); ++ ++ Class<? extends T> pluginClass; ++ try { ++ pluginClass = jarClass.asSubclass(classType); ++ } catch (ClassCastException ex) { ++ throw new ClassCastException("class '%s' does not extend '%s'".formatted(clazz, classType)); ++ } ++ ++ clazzInstance = pluginClass.getDeclaredConstructor().newInstance(); ++ } catch (IllegalAccessException exception) { ++ throw new RuntimeException("No public constructor"); ++ } catch (InstantiationException exception) { ++ throw new RuntimeException("Abnormal class instantiation", exception); ++ } ++ ++ return clazzInstance; ++ } catch (Throwable e) { ++ if (onError != null) { ++ onError.run(); ++ } ++ SneakyThrow.sneaky(e); ++ } ++ ++ throw new AssertionError(); // Shouldn't happen ++ } ++ ++} +diff --git a/src/main/java/org/bukkit/UnsafeValues.java b/src/main/java/org/bukkit/UnsafeValues.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/UnsafeValues.java ++++ b/src/main/java/org/bukkit/UnsafeValues.java +@@ -0,0 +0,0 @@ public interface UnsafeValues { + String getTranslationKey(EntityType entityType); + + String getTranslationKey(ItemStack itemStack); ++ ++ // Paper start ++ @Deprecated(forRemoval = true) ++ boolean isSupportedApiVersion(String apiVersion); ++ ++ @Deprecated(forRemoval = true) ++ static boolean isLegacyPlugin(org.bukkit.plugin.Plugin plugin) { ++ return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion()); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/command/PluginCommand.java b/src/main/java/org/bukkit/command/PluginCommand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/command/PluginCommand.java ++++ b/src/main/java/org/bukkit/command/PluginCommand.java +@@ -0,0 +0,0 @@ public final class PluginCommand extends Command implements PluginIdentifiableCo + private CommandExecutor executor; + private TabCompleter completer; + +- protected PluginCommand(@NotNull String name, @NotNull Plugin owner) { ++ PluginCommand(@NotNull String name, @NotNull Plugin owner) { + super(name); + this.executor = owner; + this.owningPlugin = owner; +diff --git a/src/main/java/org/bukkit/command/SimpleCommandMap.java b/src/main/java/org/bukkit/command/SimpleCommandMap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java ++++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java +@@ -0,0 +0,0 @@ public class SimpleCommandMap implements CommandMap { + private void setDefaultCommands() { + register("bukkit", new VersionCommand("version")); + register("bukkit", new ReloadCommand("reload")); +- register("bukkit", new PluginsCommand("plugins")); ++ //register("bukkit", new PluginsCommand("plugins")); // Paper + register("bukkit", new TimingsCommand("timings")); + } + +diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/Plugin.java ++++ b/src/main/java/org/bukkit/plugin/Plugin.java +@@ -0,0 +0,0 @@ public interface Plugin extends TabExecutor { + * Returns the plugin.yaml file containing the details for this plugin + * + * @return Contents of the plugin.yaml file ++ * @deprecated May be inaccurate due to different plugin implementations. ++ * @see Plugin#getPluginMeta() + */ ++ @Deprecated // Paper + @NotNull + public PluginDescriptionFile getDescription(); + ++ // Paper start ++ /** ++ * Gets the plugin meta for this plugin. ++ * @return configuration ++ */ ++ @NotNull ++ io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta(); ++ // Paper end + /** + * Gets a {@link FileConfiguration} for this plugin, read through + * "config.yml" +@@ -0,0 +0,0 @@ public interface Plugin extends TabExecutor { + * + * @return PluginLoader that controls this plugin + */ ++ @Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future + @NotNull + public PluginLoader getPluginLoader(); + +diff --git a/src/main/java/org/bukkit/plugin/PluginBase.java b/src/main/java/org/bukkit/plugin/PluginBase.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/PluginBase.java ++++ b/src/main/java/org/bukkit/plugin/PluginBase.java +@@ -0,0 +0,0 @@ public abstract class PluginBase implements Plugin { + @Override + @NotNull + public final String getName() { +- return getDescription().getName(); ++ return getPluginMeta().getName(); // Paper + } + } +diff --git a/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java b/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java ++++ b/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java +@@ -0,0 +0,0 @@ import org.yaml.snakeyaml.nodes.Tag; + * inferno.burningdeaths: true + *</pre></blockquote> + */ +-public final class PluginDescriptionFile { ++public final class PluginDescriptionFile implements io.papermc.paper.plugin.configuration.PluginMeta { // Paper + private static final Pattern VALID_NAME = Pattern.compile("^[A-Za-z0-9 _.-]+$"); + private static final ThreadLocal<Yaml> YAML = new ThreadLocal<Yaml>() { + @Override +@@ -0,0 +0,0 @@ public final class PluginDescriptionFile { + private Set<PluginAwareness> awareness = ImmutableSet.of(); + private String apiVersion = null; + private List<String> libraries = ImmutableList.of(); ++ // Paper start - oh my goddddd ++ /** ++ * Don't use this. ++ */ ++ @org.jetbrains.annotations.ApiStatus.Internal ++ public PluginDescriptionFile(String rawName, String name, List<String> provides, String main, String classLoaderOf, List<String> depend, List<String> softDepend, List<String> loadBefore, String version, Map<String, Map<String, Object>> commands, String description, List<String> authors, List<String> contributors, String website, String prefix, PluginLoadOrder order, List<Permission> permissions, PermissionDefault defaultPerm, Set<PluginAwareness> awareness, String apiVersion, List<String> libraries) { ++ this.rawName = rawName; ++ this.name = name; ++ this.provides = provides; ++ this.main = main; ++ this.classLoaderOf = classLoaderOf; ++ this.depend = depend; ++ this.softDepend = softDepend; ++ this.loadBefore = loadBefore; ++ this.version = version; ++ this.commands = commands; ++ this.description = description; ++ this.authors = authors; ++ this.contributors = contributors; ++ this.website = website; ++ this.prefix = prefix; ++ this.order = order; ++ this.permissions = permissions; ++ this.defaultPerm = defaultPerm; ++ this.awareness = awareness; ++ this.apiVersion = apiVersion; ++ this.libraries = libraries; ++ } ++ ++ @Override ++ public @NotNull String getMainClass() { ++ return this.main; ++ } ++ ++ @Override ++ public @NotNull PluginLoadOrder getLoadOrder() { ++ return this.order; ++ } ++ ++ @Override ++ public @Nullable String getLoggerPrefix() { ++ return this.prefix; ++ } ++ ++ @Override ++ public @NotNull List<String> getPluginDependencies() { ++ return this.depend; ++ } ++ ++ @Override ++ public @NotNull List<String> getPluginSoftDependencies() { ++ return this.softDepend; ++ } ++ ++ @Override ++ public @NotNull List<String> getLoadBeforePlugins() { ++ return this.loadBefore; ++ } ++ ++ @Override ++ public @NotNull List<String> getProvidedPlugins() { ++ return this.provides; ++ } ++ // Paper end + + public PluginDescriptionFile(@NotNull final InputStream stream) throws InvalidDescriptionException { + loadMap(asMap(YAML.get().load(stream))); +diff --git a/src/main/java/org/bukkit/plugin/PluginLoader.java b/src/main/java/org/bukkit/plugin/PluginLoader.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/PluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/PluginLoader.java +@@ -0,0 +0,0 @@ import org.jetbrains.annotations.NotNull; + * Represents a plugin loader, which handles direct access to specific types + * of plugins + */ ++@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future + public interface PluginLoader { + + /** +diff --git a/src/main/java/org/bukkit/plugin/PluginManager.java b/src/main/java/org/bukkit/plugin/PluginManager.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/PluginManager.java ++++ b/src/main/java/org/bukkit/plugin/PluginManager.java +@@ -0,0 +0,0 @@ import org.jetbrains.annotations.Nullable; + /** + * Handles all plugin management from the Server + */ +-public interface PluginManager { ++public interface PluginManager extends io.papermc.paper.plugin.PermissionManager { // Paper + + /** + * Registers the specified plugin loader +@@ -0,0 +0,0 @@ public interface PluginManager { + * @throws IllegalArgumentException Thrown when the given Class is not a + * valid PluginLoader + */ ++ @Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future + public void registerInterface(@NotNull Class<? extends PluginLoader> loader) throws IllegalArgumentException; + + /** +@@ -0,0 +0,0 @@ public interface PluginManager { + * @return True if event timings are to be used + */ + public boolean useTimings(); ++ ++ // Paper start ++ @org.jetbrains.annotations.ApiStatus.Internal ++ boolean isTransitiveDependency(io.papermc.paper.plugin.configuration.PluginMeta pluginMeta, io.papermc.paper.plugin.configuration.PluginMeta dependencyConfig); ++ ++ /** ++ * Sets the permission manager to be used for this server. ++ * ++ * @param permissionManager permission manager ++ */ ++ @org.jetbrains.annotations.ApiStatus.Experimental ++ void overridePermissionManager(@NotNull Plugin plugin, @Nullable io.papermc.paper.plugin.PermissionManager permissionManager); ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java ++++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java +@@ -0,0 +0,0 @@ import org.jetbrains.annotations.Nullable; + /** + * Handles all plugin management from the Server + */ ++@Deprecated(forRemoval = true) // Paper - This implementation may be replaced in a future version of Paper. ++// Plugins may still reflect into this class to modify permission logic for the time being. + public final class SimplePluginManager implements PluginManager { + private final Server server; + private final Map<Pattern, PluginLoader> fileAssociations = new HashMap<Pattern, PluginLoader>(); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + private MutableGraph<String> dependencyGraph = GraphBuilder.directed().build(); + private File updateDirectory; + private final SimpleCommandMap commandMap; +- private final Map<String, Permission> permissions = new HashMap<String, Permission>(); +- private final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<Boolean, Set<Permission>>(); +- private final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<String, Map<Permissible, Boolean>>(); +- private final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<Boolean, Map<Permissible, Boolean>>(); ++ // Paper start ++ public final Map<String, Permission> permissions = new HashMap<String, Permission>(); ++ public final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<Boolean, Set<Permission>>(); ++ public final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<String, Map<Permissible, Boolean>>(); ++ public final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<Boolean, Map<Permissible, Boolean>>(); ++ public PluginManager paperPluginManager; ++ // Paper end + private boolean useTimings = false; + + public SimplePluginManager(@NotNull Server instance, @NotNull SimpleCommandMap commandMap) { +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Override + @NotNull + public Plugin[] loadPlugins(@NotNull File directory) { ++ if (true) { ++ List<Plugin> pluginList = new ArrayList<>(); ++ java.util.Collections.addAll(pluginList, this.paperPluginManager.loadPlugins(directory)); ++ return pluginList.toArray(new Plugin[0]); ++ } + Preconditions.checkArgument(directory != null, "Directory cannot be null"); + Preconditions.checkArgument(directory.isDirectory(), "Directory must be a directory"); + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Nullable + public synchronized Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException { + Preconditions.checkArgument(file != null, "File cannot be null"); ++ // Paper start ++ if (true) { ++ try { ++ return this.paperPluginManager.loadPlugin(file); ++ } catch (org.bukkit.plugin.InvalidDescriptionException ignored) { ++ return null; ++ } ++ } ++ // Paper end + + checkUpdate(file); + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Override + @Nullable + public synchronized Plugin getPlugin(@NotNull String name) { ++ if (true) {return this.paperPluginManager.getPlugin(name);} // Paper + return lookupNames.get(name.replace(' ', '_')); + } + + @Override + @NotNull + public synchronized Plugin[] getPlugins() { ++ if (true) {return this.paperPluginManager.getPlugins();} // Paper + return plugins.toArray(new Plugin[plugins.size()]); + } + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + */ + @Override + public boolean isPluginEnabled(@NotNull String name) { ++ if (true) {return this.paperPluginManager.isPluginEnabled(name);} // Paper + Plugin plugin = getPlugin(name); + + return isPluginEnabled(plugin); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + */ + @Override + public boolean isPluginEnabled(@Nullable Plugin plugin) { ++ if (true) {return this.paperPluginManager.isPluginEnabled(plugin);} // Paper + if ((plugin != null) && (plugins.contains(plugin))) { + return plugin.isEnabled(); + } else { +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void enablePlugin(@NotNull final Plugin plugin) { ++ if (true) {this.paperPluginManager.enablePlugin(plugin); return;} // Paper + if (!plugin.isEnabled()) { + List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin); + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void disablePlugins() { ++ if (true) {this.paperPluginManager.disablePlugins(); return;} // Paper + Plugin[] plugins = getPlugins(); + for (int i = plugins.length - 1; i >= 0; i--) { + disablePlugin(plugins[i]); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void disablePlugin(@NotNull final Plugin plugin) { ++ if (true) {this.paperPluginManager.disablePlugin(plugin); return;} // Paper + if (plugin.isEnabled()) { + try { + plugin.getPluginLoader().disablePlugin(plugin); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void clearPlugins() { ++ if (true) {this.paperPluginManager.clearPlugins(); return;} // Paper + synchronized (this) { + disablePlugins(); + plugins.clear(); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + */ + @Override + public void callEvent(@NotNull Event event) { ++ if (true) {this.paperPluginManager.callEvent(event); return;} // Paper + if (event.isAsynchronous()) { + if (Thread.holdsLock(this)) { + throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) { ++ if (true) {this.paperPluginManager.registerEvents(listener, plugin); return;} // Paper + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled"); + } +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + Preconditions.checkArgument(priority != null, "Priority cannot be null"); + Preconditions.checkArgument(executor != null, "Executor cannot be null"); + Preconditions.checkArgument(plugin != null, "Plugin cannot be null"); ++ if (true) {this.paperPluginManager.registerEvent(event, listener, priority, executor, plugin); return;} // Paper + + if (!plugin.isEnabled()) { + throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Override + @Nullable + public Permission getPermission(@NotNull String name) { ++ if (true) {return this.paperPluginManager.getPermission(name);} // Paper + return permissions.get(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + @Override + public void addPermission(@NotNull Permission perm) { ++ if (true) {this.paperPluginManager.addPermission(perm); return;} // Paper + addPermission(perm, true); + } + + @Deprecated + public void addPermission(@NotNull Permission perm, boolean dirty) { ++ if (true) {this.paperPluginManager.addPermission(perm); return;} // Paper - This just has a performance implication, use the better api to avoid this. + String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); + + if (permissions.containsKey(name)) { +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Override + @NotNull + public Set<Permission> getDefaultPermissions(boolean op) { ++ if (true) {return this.paperPluginManager.getDefaultPermissions(op);} // Paper + return ImmutableSet.copyOf(defaultPerms.get(op)); + } + + @Override + public void removePermission(@NotNull Permission perm) { ++ if (true) {this.paperPluginManager.removePermission(perm); return;} // Paper + removePermission(perm.getName()); + } + + @Override + public void removePermission(@NotNull String name) { ++ if (true) {this.paperPluginManager.removePermission(name); return;} // Paper + permissions.remove(name.toLowerCase(java.util.Locale.ENGLISH)); + } + + @Override + public void recalculatePermissionDefaults(@NotNull Permission perm) { ++ if (true) {this.paperPluginManager.recalculatePermissionDefaults(perm); return;} // Paper + if (perm != null && permissions.containsKey(perm.getName().toLowerCase(java.util.Locale.ENGLISH))) { + defaultPerms.get(true).remove(perm); + defaultPerms.get(false).remove(perm); +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) { ++ if (true) {this.paperPluginManager.subscribeToPermission(permission, permissible); return;} // Paper + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map<Permissible, Boolean> map = permSubs.get(name); + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) { ++ if (true) {this.paperPluginManager.unsubscribeFromPermission(permission, permissible); return;} // Paper + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map<Permissible, Boolean> map = permSubs.get(name); + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Override + @NotNull + public Set<Permissible> getPermissionSubscriptions(@NotNull String permission) { ++ if (true) {return this.paperPluginManager.getPermissionSubscriptions(permission);} // Paper + String name = permission.toLowerCase(java.util.Locale.ENGLISH); + Map<Permissible, Boolean> map = permSubs.get(name); + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ if (true) {this.paperPluginManager.subscribeToDefaultPerms(op, permissible); return;} // Paper + Map<Permissible, Boolean> map = defSubs.get(op); + + if (map == null) { +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ if (true) {this.paperPluginManager.unsubscribeFromDefaultPerms(op, permissible); return;} // Paper + Map<Permissible, Boolean> map = defSubs.get(op); + + if (map != null) { +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Override + @NotNull + public Set<Permissible> getDefaultPermSubscriptions(boolean op) { ++ if (true) {return this.paperPluginManager.getDefaultPermSubscriptions(op);} // Paper + Map<Permissible, Boolean> map = defSubs.get(op); + + if (map == null) { +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + @Override + @NotNull + public Set<Permission> getPermissions() { ++ if (true) {return this.paperPluginManager.getPermissions();} // Paper + return new HashSet<Permission>(permissions.values()); + } + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + + @Override + public boolean useTimings() { ++ if (true) {return this.paperPluginManager.useTimings();} // Paper + return useTimings; + } + +@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { + public void useTimings(boolean use) { + useTimings = use; + } ++ ++ // Paper start ++ public void clearPermissions() { ++ if (true) {this.paperPluginManager.clearPermissions(); return;} // Paper ++ permissions.clear(); ++ defaultPerms.get(true).clear(); ++ defaultPerms.get(false).clear(); ++ } ++ ++ @Override ++ public boolean isTransitiveDependency(io.papermc.paper.plugin.configuration.PluginMeta pluginMeta, io.papermc.paper.plugin.configuration.PluginMeta dependencyConfig) { ++ return this.paperPluginManager.isTransitiveDependency(pluginMeta, dependencyConfig); ++ } ++ ++ @Override ++ public void overridePermissionManager(@NotNull Plugin plugin, @Nullable io.papermc.paper.plugin.PermissionManager permissionManager) { ++ this.paperPluginManager.overridePermissionManager(plugin, permissionManager); ++ } ++ ++ @Override ++ public void addPermissions(@NotNull List<Permission> perm) { ++ this.paperPluginManager.addPermissions(perm); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java ++++ b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java +@@ -0,0 +0,0 @@ public class UnknownDependencyException extends RuntimeException { + public UnknownDependencyException() { + + } ++ // Paper start ++ /** ++ * Create a new {@link UnknownDependencyException} with a message informing ++ * about which dependencies are missing for what plugin. ++ * ++ * @param missingDependencies missing dependencies ++ * @param pluginName plugin which is missing said dependencies ++ */ ++ public UnknownDependencyException(final @org.jetbrains.annotations.NotNull java.util.Collection<String> missingDependencies, final @org.jetbrains.annotations.NotNull String pluginName) { ++ this("Unknown/missing dependency plugins: [" + String.join(", ", missingDependencies) + "]. Please download and install these plugins to run '" + pluginName + "'."); ++ } ++ // Paper end + } +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java +@@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { + private Server server = null; + private File file = null; + private PluginDescriptionFile description = null; ++ private io.papermc.paper.plugin.configuration.PluginMeta pluginMeta = null; // Paper + private File dataFolder = null; + private ClassLoader classLoader = null; + private boolean naggable = true; +@@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { + private PluginLogger logger = null; + + public JavaPlugin() { +- final ClassLoader classLoader = this.getClass().getClassLoader(); +- if (!(classLoader instanceof PluginClassLoader)) { +- throw new IllegalStateException("JavaPlugin requires " + PluginClassLoader.class.getName()); ++ // Paper start ++ if (this.getClass().getClassLoader() instanceof io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ configuredPluginClassLoader.init(this); ++ } else { ++ throw new IllegalStateException("JavaPlugin requires to be created by a valid classloader."); + } +- ((PluginClassLoader) classLoader).initialize(this); ++ // Paper end + } + ++ @Deprecated(forRemoval = true) // Paper + protected JavaPlugin(@NotNull final JavaPluginLoader loader, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file) { + final ClassLoader classLoader = this.getClass().getClassLoader(); + if (classLoader instanceof PluginClassLoader) { +@@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { + * Gets the associated PluginLoader responsible for this plugin + * + * @return PluginLoader that controls this plugin ++ * @deprecated Plugin loading now occurs at a point which makes it impossible to expose this ++ * behavior. This instance will only throw unsupported operation exceptions. + */ + @NotNull + @Override ++ @Deprecated(forRemoval = true) // Paper + public final PluginLoader getPluginLoader() { + return loader; + } +@@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { + * Returns the plugin.yaml file containing the details for this plugin + * + * @return Contents of the plugin.yaml file ++ * @deprecated No longer applicable to all types of plugins + */ + @NotNull + @Override ++ @Deprecated + public final PluginDescriptionFile getDescription() { + return description; + } + ++ @Nullable ++ public final io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { ++ return this.pluginMeta; ++ } ++ + @NotNull + @Override + public FileConfiguration getConfig() { +@@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { + * + * @param enabled true if enabled, otherwise false + */ +- protected final void setEnabled(final boolean enabled) { ++ @org.jetbrains.annotations.ApiStatus.Internal // Paper ++ public final void setEnabled(final boolean enabled) { // Paper + if (isEnabled != enabled) { + isEnabled = enabled; + +@@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { + } + } + +- +- final void init(@NotNull PluginLoader loader, @NotNull Server server, @NotNull PluginDescriptionFile description, @NotNull File dataFolder, @NotNull File file, @NotNull ClassLoader classLoader) { +- this.loader = loader; ++ // Paper start ++ public final void init(@NotNull PluginLoader loader, @NotNull Server server, @NotNull PluginDescriptionFile description, @NotNull File dataFolder, @NotNull File file, @NotNull ClassLoader classLoader) { ++ init(server, description, dataFolder, file, classLoader, description); ++ this.pluginMeta = description; ++ } ++ public final void init(@NotNull Server server, @NotNull PluginDescriptionFile description, @NotNull File dataFolder, @NotNull File file, @NotNull ClassLoader classLoader, @Nullable io.papermc.paper.plugin.configuration.PluginMeta configuration) { ++ // Paper end ++ this.loader = new io.papermc.paper.plugin.provider.util.DummyBukkitPluginLoader(this); + this.server = server; + this.file = file; + this.description = description; +@@ -0,0 +0,0 @@ public abstract class JavaPlugin extends PluginBase { + this.classLoader = classLoader; + this.configFile = new File(dataFolder, "config.yml"); + this.logger = new PluginLogger(this); ++ this.pluginMeta = configuration; // Paper + } + + /** +diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java +@@ -0,0 +0,0 @@ import org.yaml.snakeyaml.error.YAMLException; + /** + * Represents a Java plugin loader, allowing plugins in the form of .jar + */ ++@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future. This implementation will be moved. + public final class JavaPluginLoader implements PluginLoader { + final Server server; + private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")}; +@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { + @Override + @NotNull + public Plugin loadPlugin(@NotNull final File file) throws InvalidPluginException { ++ if (true) throw new UnsupportedOperationException(); // Paper + Preconditions.checkArgument(file != null, "File cannot be null"); + + if (!file.exists()) { +@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { + + final PluginClassLoader loader; + try { +- loader = new PluginClassLoader(this, getClass().getClassLoader(), description, dataFolder, file, (libraryLoader != null) ? libraryLoader.createLoader(description) : null); ++ loader = new PluginClassLoader(getClass().getClassLoader(), description, dataFolder, file, (libraryLoader != null) ? libraryLoader.createLoader(description) : null, null); // Paper + } catch (InvalidPluginException ex) { + throw ex; + } catch (Throwable ex) { +diff --git a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java +@@ -0,0 +0,0 @@ import org.eclipse.aether.transport.http.HttpTransporterFactory; + import org.jetbrains.annotations.NotNull; + import org.jetbrains.annotations.Nullable; + +-class LibraryLoader ++// Paper start ++@org.jetbrains.annotations.ApiStatus.Internal ++public class LibraryLoader ++// Paper end + { + + private final Logger logger; +diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java ++++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +@@ -0,0 +0,0 @@ import org.jetbrains.annotations.Nullable; + /** + * A ClassLoader for plugins, to allow shared classes across multiple plugins + */ +-final class PluginClassLoader extends URLClassLoader { ++@org.jetbrains.annotations.ApiStatus.Internal // Paper ++public final class PluginClassLoader extends URLClassLoader implements io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader { // Paper + private final JavaPluginLoader loader; + private final Map<String, Class<?>> classes = new ConcurrentHashMap<String, Class<?>>(); + private final PluginDescriptionFile description; +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + private final Manifest manifest; + private final URL url; + private final ClassLoader libraryLoader; +- final JavaPlugin plugin; ++ public final JavaPlugin plugin; // Paper + private JavaPlugin pluginInit; + private IllegalStateException pluginState; + private final Set<String> seenIllegalAccess = Collections.newSetFromMap(new ConcurrentHashMap<>()); ++ private java.util.logging.Logger logger; // Paper - add field ++ private io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup classLoaderGroup; // Paper ++ public io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext; // Paper + + static { + ClassLoader.registerAsParallelCapable(); + } + +- PluginClassLoader(@NotNull final JavaPluginLoader loader, @Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader) throws IOException, InvalidPluginException, MalformedURLException { ++ @org.jetbrains.annotations.ApiStatus.Internal // Paper ++ public PluginClassLoader(@Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader, io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext) throws IOException, InvalidPluginException, MalformedURLException { // Paper + super(new URL[] {file.toURI().toURL()}, parent); +- Preconditions.checkArgument(loader != null, "Loader cannot be null"); ++ this.loader = null; // Paper - pass null into loader field + +- this.loader = loader; + this.description = description; + this.dataFolder = dataFolder; + this.file = file; +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + this.url = file.toURI().toURL(); + this.libraryLoader = libraryLoader; + ++ ++ // Paper start ++ this.classLoaderGroup = io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage.instance().registerSpigotGroup(this); // Paper ++ this.dependencyContext = dependencyContext; ++ // Paper end + try { + Class<?> jarClass; + try { +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + return findResources(name); + } + ++ // Paper start ++ @Override ++ public Class<?> loadClass(@NotNull String name, boolean resolve, boolean checkGlobal, boolean checkLibraries) throws ClassNotFoundException { ++ return this.loadClass0(name, resolve, checkGlobal, checkLibraries); ++ } ++ @Override ++ public io.papermc.paper.plugin.configuration.PluginMeta getConfiguration() { ++ return this.description; ++ } ++ ++ @Override ++ public void init(JavaPlugin plugin) { ++ this.initialize(plugin); ++ } ++ // Paper end ++ + @Override + protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { + return loadClass0(name, resolve, true, true); +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + + if (checkGlobal) { + // This ignores the libraries of other plugins, unless they are transitive dependencies. +- Class<?> result = loader.getClassByName(name, resolve, description); ++ Class<?> result = this.classLoaderGroup.getClassByName(name, resolve, this); // Paper + + if (result != null) { + // If the class was loaded from a library instead of a PluginClassLoader, we can assume that its associated plugin is a transitive dependency and can therefore skip this check. +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + + if (provider != description + && !seenIllegalAccess.contains(provider.getName()) +- && !((SimplePluginManager) loader.server.getPluginManager()).isTransitiveDepend(description, provider)) { ++ && !this.dependencyContext.isTransitiveDependency(description, provider)) { // Paper + + seenIllegalAccess.add(provider.getName()); + if (plugin != null) { +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + throw new ClassNotFoundException(name, ex); + } + +- classBytes = loader.server.getUnsafe().processClass(description, path, classBytes); ++ classBytes = org.bukkit.Bukkit.getServer().getUnsafe().processClass(description, path, classBytes); // Paper + + int dot = name.lastIndexOf('.'); + if (dot != -1) { +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + result = super.findClass(name); + } + +- loader.setClass(name, result); + classes.put(name, result); ++ this.setClass(name, result); // Paper + } + + return result; +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + @Override + public void close() throws IOException { + try { ++ // Paper start ++ Collection<Class<?>> classes = getClasses(); ++ for (Class<?> clazz : classes) { ++ removeClass(clazz); ++ } ++ // Paper end + super.close(); + } finally { + jar.close(); +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + return classes.values(); + } + +- synchronized void initialize(@NotNull JavaPlugin javaPlugin) { ++ public synchronized void initialize(@NotNull JavaPlugin javaPlugin) { // Paper + Preconditions.checkArgument(javaPlugin != null, "Initializing plugin cannot be null"); + Preconditions.checkArgument(javaPlugin.getClass().getClassLoader() == this, "Cannot initialize plugin outside of this class loader"); + if (this.plugin != null || this.pluginInit != null) { +@@ -0,0 +0,0 @@ final class PluginClassLoader extends URLClassLoader { + pluginState = new IllegalStateException("Initial initialization"); + this.pluginInit = javaPlugin; + +- javaPlugin.init(loader, loader.server, description, dataFolder, file, this); ++ javaPlugin.init(null, org.bukkit.Bukkit.getServer(), description, dataFolder, file, this); // Paper ++ } ++ ++ // Paper start ++ @Override ++ public String toString() { ++ JavaPlugin currPlugin = plugin != null ? plugin : pluginInit; ++ return "PluginClassLoader{" + ++ "plugin=" + currPlugin + ++ ", pluginEnabled=" + (currPlugin == null ? "uninitialized" : currPlugin.isEnabled()) + ++ ", url=" + file + ++ '}'; ++ } ++ ++ void setClass(@NotNull final String name, @NotNull final Class<?> clazz) { ++ if (org.bukkit.configuration.serialization.ConfigurationSerializable.class.isAssignableFrom(clazz)) { ++ Class<? extends org.bukkit.configuration.serialization.ConfigurationSerializable> serializable = clazz.asSubclass(org.bukkit.configuration.serialization.ConfigurationSerializable.class); ++ org.bukkit.configuration.serialization.ConfigurationSerialization.registerClass(serializable); ++ } ++ } ++ ++ private void removeClass(@NotNull Class<?> clazz) { ++ if (org.bukkit.configuration.serialization.ConfigurationSerializable.class.isAssignableFrom(clazz)) { ++ Class<? extends org.bukkit.configuration.serialization.ConfigurationSerializable> serializable = clazz.asSubclass(org.bukkit.configuration.serialization.ConfigurationSerializable.class); ++ org.bukkit.configuration.serialization.ConfigurationSerialization.unregisterClass(serializable); ++ } + } ++ // Paper end + } +diff --git a/src/test/java/io/papermc/paper/testing/TestServer.java b/src/test/java/io/papermc/paper/testing/TestServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/test/java/io/papermc/paper/testing/TestServer.java ++++ b/src/test/java/io/papermc/paper/testing/TestServer.java +@@ -0,0 +0,0 @@ public final class TestServer { + + when(dummyServer.getTag(anyString(), any(NamespacedKey.class), any())).thenAnswer(ignored -> new EmptyTag()); + +- final PluginManager pluginManager = new SimplePluginManager(dummyServer, new SimpleCommandMap(dummyServer)); +- when(dummyServer.getPluginManager()).thenReturn(pluginManager); +- + when(dummyServer.getRegistry(any())).thenAnswer(ignored -> new EmptyRegistry()); + when(dummyServer.getScoreboardCriteria(anyString())).thenReturn(null); + +diff --git a/src/test/java/org/bukkit/event/SyntheticEventTest.java b/src/test/java/org/bukkit/event/SyntheticEventTest.java +deleted file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- a/src/test/java/org/bukkit/event/SyntheticEventTest.java ++++ /dev/null +@@ -0,0 +0,0 @@ +-package org.bukkit.event; +- +-import org.bukkit.plugin.PluginLoader; +-import org.bukkit.plugin.SimplePluginManager; +-import org.bukkit.plugin.TestPlugin; +-import org.bukkit.plugin.java.JavaPluginLoader; +-import org.junit.Assert; +-import org.junit.Test; +- +-public class SyntheticEventTest { +- @SuppressWarnings("deprecation") +- @Test +- public void test() { +- io.papermc.paper.testing.TestServer.setup(); // Paper +- final JavaPluginLoader loader = new JavaPluginLoader(org.bukkit.Bukkit.getServer()); // Paper +- TestPlugin plugin = new TestPlugin(getClass().getName()) { +- @Override +- public PluginLoader getPluginLoader() { +- return loader; +- } +- }; +- SimplePluginManager pluginManager = new SimplePluginManager(org.bukkit.Bukkit.getServer(), null); // Paper +- +- TestEvent event = new TestEvent(false); +- Impl impl = new Impl(); +- +- pluginManager.registerEvents(impl, plugin); +- pluginManager.callEvent(event); +- +- Assert.assertEquals(1, impl.callCount); +- } +- +- public abstract static class Base<E extends Event> implements Listener { +- int callCount = 0; +- +- public void accept(E evt) { +- callCount++; +- } +- } +- +- public static class Impl extends Base<TestEvent> { +- @Override +- @EventHandler +- public void accept(TestEvent evt) { +- super.accept(evt); +- } +- } +-} +diff --git a/src/test/java/org/bukkit/plugin/PluginManagerTest.java b/src/test/java/org/bukkit/plugin/PluginManagerTest.java +deleted file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- a/src/test/java/org/bukkit/plugin/PluginManagerTest.java ++++ /dev/null +@@ -0,0 +0,0 @@ +-package org.bukkit.plugin; +- +-import static org.hamcrest.Matchers.*; +-import static org.junit.Assert.*; +-import org.bukkit.event.Event; +-import org.bukkit.event.TestEvent; +-import org.bukkit.permissions.Permission; +-import org.junit.After; +-import org.junit.Test; +- +-public class PluginManagerTest { +- private class MutableObject { +- volatile Object value = null; +- } +- +- private static final PluginManager pm = org.bukkit.Bukkit.getServer().getPluginManager(); // Paper +- +- private final MutableObject store = new MutableObject(); +- +- @Test +- public void testAsyncSameThread() { +- final Event event = new TestEvent(true); +- try { +- pm.callEvent(event); +- } catch (IllegalStateException ex) { +- assertThat(event.getEventName() + " cannot be triggered asynchronously from primary server thread.", is(ex.getMessage())); +- return; +- } +- throw new IllegalStateException("No exception thrown"); +- } +- +- @Test +- public void testSyncSameThread() { +- final Event event = new TestEvent(false); +- pm.callEvent(event); +- } +- +- @Test +- public void testAsyncLocked() throws InterruptedException { +- final Event event = new TestEvent(true); +- Thread secondThread = new Thread( +- new Runnable() { +- @Override +- public void run() { +- try { +- synchronized (pm) { +- pm.callEvent(event); +- } +- } catch (Throwable ex) { +- store.value = ex; +- } +- } +- } +- ); +- secondThread.start(); +- secondThread.join(); +- assertThat(store.value, is(instanceOf(IllegalStateException.class))); +- assertThat(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.", is(((Throwable) store.value).getMessage())); +- } +- +- @Test +- public void testAsyncUnlocked() throws InterruptedException { +- final Event event = new TestEvent(true); +- Thread secondThread = new Thread( +- new Runnable() { +- @Override +- public void run() { +- try { +- pm.callEvent(event); +- } catch (Throwable ex) { +- store.value = ex; +- } +- } +- } +- ); +- secondThread.start(); +- secondThread.join(); +- if (store.value != null) { +- throw new RuntimeException((Throwable) store.value); +- } +- } +- +- @Test +- public void testSyncUnlocked() throws InterruptedException { +- final Event event = new TestEvent(false); +- Thread secondThread = new Thread( +- new Runnable() { +- @Override +- public void run() { +- try { +- pm.callEvent(event); +- } catch (Throwable ex) { +- store.value = ex; +- assertThat(event.getEventName() + " cannot be triggered asynchronously from another thread.", is(ex.getMessage())); +- return; +- } +- } +- } +- ); +- secondThread.start(); +- secondThread.join(); +- if (store.value == null) { +- throw new IllegalStateException("No exception thrown"); +- } +- } +- +- @Test +- public void testSyncLocked() throws InterruptedException { +- final Event event = new TestEvent(false); +- Thread secondThread = new Thread( +- new Runnable() { +- @Override +- public void run() { +- try { +- synchronized (pm) { +- pm.callEvent(event); +- } +- } catch (Throwable ex) { +- store.value = ex; +- assertThat(event.getEventName() + " cannot be triggered asynchronously from another thread.", is(ex.getMessage())); +- return; +- } +- } +- } +- ); +- secondThread.start(); +- secondThread.join(); +- if (store.value == null) { +- throw new IllegalStateException("No exception thrown"); +- } +- } +- +- @Test +- public void testRemovePermissionByNameLower() { +- this.testRemovePermissionByName("lower"); +- } +- +- @Test +- public void testRemovePermissionByNameUpper() { +- this.testRemovePermissionByName("UPPER"); +- } +- +- @Test +- public void testRemovePermissionByNameCamel() { +- this.testRemovePermissionByName("CaMeL"); +- } +- +- public void testRemovePermissionByPermissionLower() { +- this.testRemovePermissionByPermission("lower"); +- } +- +- @Test +- public void testRemovePermissionByPermissionUpper() { +- this.testRemovePermissionByPermission("UPPER"); +- } +- +- @Test +- public void testRemovePermissionByPermissionCamel() { +- this.testRemovePermissionByPermission("CaMeL"); +- } +- +- private void testRemovePermissionByName(final String name) { +- final Permission perm = new Permission(name); +- pm.addPermission(perm); +- assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm)); +- pm.removePermission(name); +- assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue())); +- } +- +- private void testRemovePermissionByPermission(final String name) { +- final Permission perm = new Permission(name); +- pm.addPermission(perm); +- assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm)); +- pm.removePermission(perm); +- assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue())); +- } +- +- @After +- public void tearDown() { +- pm.clearPlugins(); +- assertThat(pm.getPermissions(), is(empty())); +- } +-} +diff --git a/src/test/java/org/bukkit/plugin/TestPlugin.java b/src/test/java/org/bukkit/plugin/TestPlugin.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/test/java/org/bukkit/plugin/TestPlugin.java ++++ b/src/test/java/org/bukkit/plugin/TestPlugin.java +@@ -0,0 +0,0 @@ public class TestPlugin extends PluginBase { + public PluginDescriptionFile getDescription() { + return new PluginDescriptionFile(pluginName, "1.0", "test.test"); + } ++ // Paper start ++ @Override ++ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { ++ return getDescription(); ++ } ++ // Paper end + + @Override + public FileConfiguration getConfig() { diff --git a/patches/api/Prioritise-own-classes-where-possible.patch b/patches/api/Prioritise-own-classes-where-possible.patch deleted file mode 100644 index 9f75bf62cc..0000000000 --- a/patches/api/Prioritise-own-classes-where-possible.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Mariell Hoversholm <proximyst@proximyst.com> -Date: Mon, 27 Apr 2020 12:31:59 +0200 -Subject: [PATCH] Prioritise own classes where possible - -This adds the server property `Paper.DisableClassPrioritization` to disable -prioritization of own classes for plugins' classloaders. - -This value is by default not present, and this will therefore break any -plugins which abuse behaviour related to not using their own classes -while still loading their own. This is often an issue with failing to -relocate or shade properly, such as when shading plugin APIs like Vault. - -A plugin's classloader will first look in the same jar as it is loading -in for a requested class, then load it. It does not re-use other -plugins' classes if it has the chance to avoid doing so. - -If a class is not found in the same jar as it is loading for and it does -find it elsewhere, it will still choose the class elsewhere. This is -intended behaviour, as it will only prioritise classes it has in its own -jar, no other plugins' classes will be prioritised in any other order -than the one they were registered in. - -The patch in general terms just loads the class in the plugin's jar -before it starts looking elsewhere for it. - -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ import org.yaml.snakeyaml.error.YAMLException; - */ - public final class JavaPluginLoader implements PluginLoader { - final Server server; -+ private static final boolean DISABLE_CLASS_PRIORITIZATION = Boolean.getBoolean("Paper.DisableClassPrioritization"); // Paper - private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")}; - private final Map<String, java.util.concurrent.locks.ReentrantReadWriteLock> classLoadLock = new java.util.HashMap<String, java.util.concurrent.locks.ReentrantReadWriteLock>(); // Paper - private final Map<String, Integer> classLoadLockCount = new java.util.HashMap<String, Integer>(); // Paper -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - - @Nullable - Class<?> getClassByName(final String name, boolean resolve, PluginDescriptionFile description) { -+ // Paper start - prioritize self -+ return getClassByName(name, resolve, description, null); -+ } -+ Class<?> getClassByName(final String name, boolean resolve, PluginDescriptionFile description, PluginClassLoader requester) { -+ // Paper end - // Paper start - make MT safe - java.util.concurrent.locks.ReentrantReadWriteLock lock; - synchronized (classLoadLock) { -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - classLoadLockCount.compute(name, (x, prev) -> prev != null ? prev + 1 : 1); - } - lock.writeLock().lock();try { -+ // Paper start - prioritize self -+ if (!DISABLE_CLASS_PRIORITIZATION && requester != null) { -+ try { -+ return requester.loadClass0(name, false, false, ((SimplePluginManager) server.getPluginManager()).isTransitiveDepend(description, requester.getDescription())); -+ } catch (ClassNotFoundException cnfe) {} -+ } -+ // Paper end - // Paper end - for (PluginClassLoader loader : loaders) { - try { -diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot - public JavaPlugin getPlugin() { return plugin; } // Spigot - private final JavaPluginLoader loader; - private final Map<String, Class<?>> classes = new ConcurrentHashMap<String, Class<?>>(); -- private final PluginDescriptionFile description; -+ private final PluginDescriptionFile description; PluginDescriptionFile getDescription() { return description; } // Paper - private final File dataFolder; - private final File file; - private final JarFile jar; -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot - - if (checkGlobal) { - // This ignores the libraries of other plugins, unless they are transitive dependencies. -- Class<?> result = loader.getClassByName(name, resolve, description); -+ Class<?> result = loader.getClassByName(name, resolve, description, this); // Paper - prioritize self - - if (result != null) { - // If the class was loaded from a library instead of a PluginClassLoader, we can assume that its associated plugin is a transitive dependency and can therefore skip this check. diff --git a/patches/api/Provide-a-useful-PluginClassLoader-toString.patch b/patches/api/Provide-a-useful-PluginClassLoader-toString.patch deleted file mode 100644 index 6380e62aaf..0000000000 --- a/patches/api/Provide-a-useful-PluginClassLoader-toString.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Shane Freeder <theboyetronic@gmail.com> -Date: Sun, 31 May 2020 15:26:17 +0100 -Subject: [PATCH] Provide a useful PluginClassLoader#toString - -There are several cases where the plugin classloader may be dumped to the logs, -however, this provides no indication of the owner of the classloader, making -these messages effectively useless, this patch rectifies this - -diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot - javaPlugin.logger = this.logger; // Paper - set logger - javaPlugin.init(loader, loader.server, description, dataFolder, file, this); - } -+ -+ // Paper start -+ @Override -+ public String toString() { -+ JavaPlugin currPlugin = plugin != null ? plugin : pluginInit; -+ return "PluginClassLoader{" + -+ "plugin=" + currPlugin + -+ ", pluginEnabled=" + (currPlugin == null ? "uninitialized" : currPlugin.isEnabled()) + -+ ", url=" + file + -+ '}'; -+ } -+ // Paper end - } diff --git a/patches/api/Remove-deadlock-risk-in-firing-async-events.patch b/patches/api/Remove-deadlock-risk-in-firing-async-events.patch deleted file mode 100644 index b41013e8f4..0000000000 --- a/patches/api/Remove-deadlock-risk-in-firing-async-events.patch +++ /dev/null @@ -1,124 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Aikar <aikar@aikar.co> -Date: Sun, 9 Sep 2018 00:32:05 -0400 -Subject: [PATCH] Remove deadlock risk in firing async events - -The PluginManager incorrectly used synchronization on firing any event -that was marked as synchronous. - -This synchronized did not even protect any concurrency risk as -handlers were already thread safe in terms of mutations during event -dispatch. - -The way it was used, has commonly led to deadlocks on the server, -which results in a hard crash. - -This change removes the synchronize and adds some protection around enable/disable - -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - * @return true if the plugin is enabled, otherwise false - */ - @Override -- public boolean isPluginEnabled(@Nullable Plugin plugin) { -+ public synchronized boolean isPluginEnabled(@Nullable Plugin plugin) { // Paper - synchronize - if ((plugin != null) && (plugins.contains(plugin))) { - return plugin.isEnabled(); - } else { -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - } - - @Override -- public void enablePlugin(@NotNull final Plugin plugin) { -+ public synchronized void enablePlugin(@NotNull final Plugin plugin) { // Paper - synchronize - if (!plugin.isEnabled()) { - List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin); - -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - // Paper end - - @Override -- public void disablePlugin(@NotNull final Plugin plugin) { -+ public synchronized void disablePlugin(@NotNull final Plugin plugin) { // Paper - synchronize - if (plugin.isEnabled()) { - try { - plugin.getPluginLoader().disablePlugin(plugin); -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - defaultPerms.get(false).clear(); - } - } -+ private void fireEvent(Event event) { callEvent(event); } // Paper - support old method incase plugin uses reflection - - /** - * Calls an event with the given details. -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - */ - @Override - public void callEvent(@NotNull Event event) { -- if (event.isAsynchronous()) { -- if (Thread.holdsLock(this)) { -- throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code."); -- } -- if (server.isPrimaryThread()) { -- throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from primary server thread."); -- } -- } else { -- if (!server.isPrimaryThread()) { -- throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from another thread."); -- } -+ // Paper - replace callEvent by merging to below method -+ if (event.isAsynchronous() && server.isPrimaryThread()) { -+ throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); -+ } else if (!event.isAsynchronous() && !server.isPrimaryThread()) { -+ throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); - } - -- fireEvent(event); -- } -- -- private void fireEvent(@NotNull Event event) { - HandlerList handlers = event.getHandlers(); - RegisteredListener[] listeners = handlers.getRegisteredListeners(); - -diff --git a/src/test/java/org/bukkit/plugin/PluginManagerTest.java b/src/test/java/org/bukkit/plugin/PluginManagerTest.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/test/java/org/bukkit/plugin/PluginManagerTest.java -+++ b/src/test/java/org/bukkit/plugin/PluginManagerTest.java -@@ -0,0 +0,0 @@ public class PluginManagerTest { - private static final PluginManager pm = org.bukkit.Bukkit.getServer().getPluginManager(); // Paper - - private final MutableObject store = new MutableObject(); -- -+/* // Paper start - remove unneeded test - @Test - public void testAsyncSameThread() { - final Event event = new TestEvent(true); -@@ -0,0 +0,0 @@ public class PluginManagerTest { - return; - } - throw new IllegalStateException("No exception thrown"); -- } -+ }*/ // Paper end - - @Test - public void testSyncSameThread() { - final Event event = new TestEvent(false); - pm.callEvent(event); - } -- -+/* // Paper start - remove unneeded test - @Test - public void testAsyncLocked() throws InterruptedException { - final Event event = new TestEvent(true); -@@ -0,0 +0,0 @@ public class PluginManagerTest { - if (store.value == null) { - throw new IllegalStateException("No exception thrown"); - } -- } -+ } */ // Paper - - @Test - public void testRemovePermissionByNameLower() { diff --git a/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch b/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch index e13e260a58..f790f54550 100644 --- a/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch +++ b/patches/api/Rewrite-LogEvents-to-contain-the-source-jars-in-stac.patch @@ -8,12 +8,12 @@ diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/m index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java -@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader { // Spigot - } +@@ -0,0 +0,0 @@ public final class PluginClassLoader extends URLClassLoader implements io.paperm - PluginClassLoader(@NotNull final JavaPluginLoader loader, @Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader) throws IOException, InvalidPluginException, MalformedURLException { + @org.jetbrains.annotations.ApiStatus.Internal // Paper + public PluginClassLoader(@Nullable final ClassLoader parent, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file, @Nullable ClassLoader libraryLoader, io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext) throws IOException, InvalidPluginException, MalformedURLException { // Paper - super(new URL[] {file.toURI().toURL()}, parent); -+ super(file.getName(), new URL[] {file.toURI().toURL()}, parent); // Paper - rewrite LogEvents to contain source jar info - Preconditions.checkArgument(loader != null, "Loader cannot be null"); ++ super(file.getName(), new URL[] {file.toURI().toURL()}, parent); + this.loader = null; // Paper - pass null into loader field - this.loader = loader; + this.description = description; diff --git a/patches/api/Test-changes.patch b/patches/api/Test-changes.patch index b7039e17b4..362859abbb 100644 --- a/patches/api/Test-changes.patch +++ b/patches/api/Test-changes.patch @@ -230,6 +230,18 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 private static void collectClasses(@NotNull File from, @NotNull Map<String, ClassNode> to) throws IOException { @@ -0,0 +0,0 @@ public class AnnotationTest { + // Exceptions are excluded + return false; + } ++ // Paper start ++ if (isInternal(clazz.invisibleAnnotations)) { ++ return false; ++ } ++ // Paper end + + for (String excludedClass : EXCLUDED_CLASSES) { + if (excludedClass.equals(clazz.name)) { +@@ -0,0 +0,0 @@ public class AnnotationTest { private static boolean isMethodIncluded(@NotNull ClassNode clazz, @NotNull MethodNode method, @NotNull Map<String, ClassNode> allClasses) { // Exclude private, synthetic and deprecated methods @@ -239,8 +251,31 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } @@ -0,0 +0,0 @@ public class AnnotationTest { + if ("<init>".equals(method.name) && isAnonymous(clazz)) { + return false; + } ++ // Paper start ++ if (isInternal(method.invisibleAnnotations)) { ++ return false; ++ } ++ // Paper end + return true; } ++ // Paper start ++ private static boolean isInternal(List<? extends AnnotationNode> annotations) { ++ if (annotations == null) { ++ return false; ++ } ++ for (AnnotationNode node : annotations) { ++ if (node.desc.equals("Lorg/jetbrains/annotations/ApiStatus$Internal;")) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ // Paper end - private static boolean isWellAnnotated(@Nullable List<AnnotationNode> annotations) { + private static boolean isWellAnnotated(@Nullable List<? extends AnnotationNode> annotations) { // Paper diff --git a/patches/api/Timings-v2.patch b/patches/api/Timings-v2.patch index ae392c11ef..ad8e63cc03 100644 --- a/patches/api/Timings-v2.patch +++ b/patches/api/Timings-v2.patch @@ -2846,9 +2846,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 Material fromLegacy(Material material); @@ -0,0 +0,0 @@ public interface UnsafeValues { - String getTranslationKey(EntityType entityType); - - String getTranslationKey(ItemStack itemStack); + return !Bukkit.getUnsafe().isSupportedApiVersion(plugin.getDescription().getAPIVersion()); + } + // Paper end + + // Paper start + /** @@ -2893,6 +2893,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 protected String usageMessage; private String permission; private net.kyori.adventure.text.Component permissionMessage; // Paper +- public org.spigotmc.CustomTimingsHandler timings; // Spigot + public co.aikar.timings.Timing timings; // Paper + @NotNull public String getTimingName() {return getName();} // Paper @@ -3093,7 +3094,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public class SimpleCommandMap implements CommandMap { register("bukkit", new VersionCommand("version")); register("bukkit", new ReloadCommand("reload")); - register("bukkit", new PluginsCommand("plugins")); + //register("bukkit", new PluginsCommand("plugins")); // Paper - register("bukkit", new TimingsCommand("timings")); + register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Paper } @@ -3434,9 +3435,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } @@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - @Override @Nullable public synchronized Plugin getPlugin(@NotNull String name) { + if (true) {return this.paperPluginManager.getPlugin(name);} // Paper - return lookupNames.get(name.replace(' ', '_')); + return lookupNames.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); // Paper } @@ -3453,9 +3454,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } else { getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); @@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - @Override public boolean useTimings() { + if (true) {return this.paperPluginManager.useTimings();} // Paper - return useTimings; + return co.aikar.timings.Timings.isTimingsEnabled(); // Spigot } @@ -3468,7 +3469,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - useTimings = use; + co.aikar.timings.Timings.setTimingsEnabled(use); // Paper } - } + + // Paper start diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java @@ -3517,11 +3519,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java +++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java @@ -0,0 +0,0 @@ import org.jetbrains.annotations.Nullable; - /** - * A ClassLoader for plugins, to allow shared classes across multiple plugins */ --final class PluginClassLoader extends URLClassLoader { -+public final class PluginClassLoader extends URLClassLoader { // Spigot + @org.jetbrains.annotations.ApiStatus.Internal // Paper + public final class PluginClassLoader extends URLClassLoader implements io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader { // Paper + public JavaPlugin getPlugin() { return plugin; } // Spigot private final JavaPluginLoader loader; private final Map<String, Class<?>> classes = new ConcurrentHashMap<String, Class<?>>(); diff --git a/patches/api/Update-Folder-Uses-Plugin-Name.patch b/patches/api/Update-Folder-Uses-Plugin-Name.patch deleted file mode 100644 index 3f59b529ec..0000000000 --- a/patches/api/Update-Folder-Uses-Plugin-Name.patch +++ /dev/null @@ -1,86 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Xemorr <31805746+Xemorr@users.noreply.github.com> -Date: Fri, 1 Apr 2022 19:57:40 +0100 -Subject: [PATCH] Update Folder Uses Plugin Name - - -diff --git a/src/main/java/org/bukkit/plugin/SimplePluginManager.java b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java -+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - public synchronized Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException { - Preconditions.checkArgument(file != null, "File cannot be null"); - -- checkUpdate(file); -+ file = checkUpdate(file); // Paper - update the reference in case checkUpdate renamed it - - Set<Pattern> filters = fileAssociations.keySet(); - Plugin result = null; -@@ -0,0 +0,0 @@ public final class SimplePluginManager implements PluginManager { - return result; - } - -- private void checkUpdate(@NotNull File file) { -+ // Paper start - Update Folder Uses Plugin Name to replace -+ /** -+ * Replaces a plugin with a plugin of the same plugin name in the update folder. -+ * @param file -+ * @throws InvalidPluginException -+ */ -+ private File checkUpdate(@NotNull File file) throws InvalidPluginException { - if (updateDirectory == null || !updateDirectory.isDirectory()) { -- return; -+ return file; -+ } -+ PluginLoader pluginLoader = getPluginLoader(file); -+ try { -+ String pluginName = pluginLoader.getPluginDescription(file).getName(); -+ for (File updateFile : updateDirectory.listFiles()) { -+ if (!updateFile.isFile()) continue; -+ PluginLoader updatePluginLoader = getPluginLoader(updateFile); -+ if (updatePluginLoader == null) continue; -+ String updatePluginName; -+ try { -+ updatePluginName = updatePluginLoader.getPluginDescription(updateFile).getName(); -+ // We failed to load this data for some reason, so, we'll skip over this -+ } catch (InvalidDescriptionException ex) { -+ continue; -+ } -+ if (!pluginName.equals(updatePluginName)) continue; -+ try { -+ java.nio.file.Files.copy(updateFile.toPath(), file.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); -+ } catch (java.io.IOException exception) { -+ server.getLogger().log(Level.SEVERE, "Could not copy '" + updateFile.getPath() + "' to '" + file.getPath() + "' in update plugin process", exception); -+ continue; -+ } -+ File newName = new File(file.getParentFile(), updateFile.getName()); -+ file.renameTo(newName); -+ updateFile.delete(); -+ return newName; -+ } - } -+ catch (InvalidDescriptionException e) { -+ throw new InvalidPluginException(e); -+ } -+ return file; -+ } - -- File updateFile = new File(updateDirectory, file.getName()); -- if (updateFile.isFile() && FileUtil.copy(updateFile, file)) { -- updateFile.delete(); -+ @Nullable -+ private PluginLoader getPluginLoader(File file) { -+ Set<Pattern> filters = fileAssociations.keySet(); -+ for (Pattern filter : filters) { -+ Matcher match = filter.matcher(file.getName()); -+ if (match.find()) { -+ return fileAssociations.get(filter); -+ } - } -+ return null; - } -+ // Paper end - - /** - * Checks if the given plugin is loaded and returns it when applicable diff --git a/patches/api/Use-ASM-for-event-executors.patch b/patches/api/Use-ASM-for-event-executors.patch index 830aa0c5fb..245cbad56f 100644 --- a/patches/api/Use-ASM-for-event-executors.patch +++ b/patches/api/Use-ASM-for-event-executors.patch @@ -368,30 +368,3 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end } -diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java -@@ -0,0 +0,0 @@ public final class JavaPluginLoader implements PluginLoader { - } - } - -- EventExecutor executor = new co.aikar.timings.TimedEventExecutor(new EventExecutor() { // Paper -- @Override -- public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException { // Paper -- try { -- if (!eventClass.isAssignableFrom(event.getClass())) { -- return; -- } -- method.invoke(listener, event); -- } catch (InvocationTargetException ex) { -- throw new EventException(ex.getCause()); -- } catch (Throwable t) { -- throw new EventException(t); -- } -- } -- }, plugin, method, eventClass); // Paper -+ EventExecutor executor = new co.aikar.timings.TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); // Paper // Paper (Yes.) - Use factory method `EventExecutor.create()` - if (false) { // Spigot - RL handles useTimings check now - eventSet.add(new TimedRegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); - } else { diff --git a/patches/server/Add-CraftMagicNumbers.isSupportedApiVersion.patch b/patches/server/Add-CraftMagicNumbers.isSupportedApiVersion.patch deleted file mode 100644 index 39ef9e26a9..0000000000 --- a/patches/server/Add-CraftMagicNumbers.isSupportedApiVersion.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: BlackHole <black-hole@live.com> -Date: Sun, 15 Dec 2019 19:12:39 +0100 -Subject: [PATCH] Add CraftMagicNumbers.isSupportedApiVersion() - - -diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues { - public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { - return new com.destroystokyo.paper.PaperVersionFetcher(); - } -+ -+ @Override -+ public boolean isSupportedApiVersion(String apiVersion) { -+ return apiVersion != null && SUPPORTED_API.contains(apiVersion); -+ } - // Paper end - - /** diff --git a/patches/server/Add-Raw-Byte-ItemStack-Serialization.patch b/patches/server/Add-Raw-Byte-ItemStack-Serialization.patch index e02614e295..ef47ed6e92 100644 --- a/patches/server/Add-Raw-Byte-ItemStack-Serialization.patch +++ b/patches/server/Add-Raw-Byte-ItemStack-Serialization.patch @@ -10,8 +10,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues { - public boolean isSupportedApiVersion(String apiVersion) { - return apiVersion != null && SUPPORTED_API.contains(apiVersion); + public com.destroystokyo.paper.util.VersionFetcher getVersionFetcher() { + return new com.destroystokyo.paper.PaperVersionFetcher(); } + + @Override diff --git a/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch b/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch index a847b68040..22811394ef 100644 --- a/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch +++ b/patches/server/Add-command-line-option-to-load-extra-plugin-jars-no.patch @@ -11,26 +11,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -0,0 +0,0 @@ public final class CraftServer implements Server { - public void loadPlugins() { - this.pluginManager.registerInterface(JavaPluginLoader.class); - -- File pluginFolder = (File) console.options.valueOf("plugins"); -+ File pluginFolder = this.getPluginsFolder(); // Paper - -- if (pluginFolder.exists()) { -- Plugin[] plugins = this.pluginManager.loadPlugins(pluginFolder); -+ // Paper start -+ if (true || pluginFolder.exists()) { -+ if (!pluginFolder.exists()) { -+ pluginFolder.mkdirs(); -+ } -+ Plugin[] plugins = this.pluginManager.loadPlugins(pluginFolder, this.extraPluginJars()); -+ // Paper end - for (Plugin plugin : plugins) { - try { - String message = String.format("Loading %s", plugin.getDescription().getFullName()); -@@ -0,0 +0,0 @@ public final class CraftServer implements Server { - } + io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.INSTANCE.enter(io.papermc.paper.plugin.entrypoint.Entrypoint.PLUGIN); // Paper - replace implementation } + // Paper start diff --git a/patches/server/Add-debug-for-sync-chunk-loads.patch b/patches/server/Add-debug-for-sync-chunk-loads.patch index 4075a8f74e..93cb7ce941 100644 --- a/patches/server/Add-debug-for-sync-chunk-loads.patch +++ b/patches/server/Add-debug-for-sync-chunk-loads.patch @@ -203,10 +203,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 import io.papermc.paper.command.subcommands.ReloadCommand; +import io.papermc.paper.command.subcommands.SyncLoadInfoCommand; import io.papermc.paper.command.subcommands.VersionCommand; + import io.papermc.paper.command.subcommands.DumpPluginsCommand; import it.unimi.dsi.fastutil.Pair; - import java.util.ArrayList; @@ -0,0 +0,0 @@ public final class PaperCommand extends Command { - commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); commands.put(Set.of("fixlight"), new FixLightCommand()); commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); + commands.put(Set.of("syncloadinfo"), new SyncLoadInfoCommand()); diff --git a/patches/server/Flat-bedrock-generator-settings.patch b/patches/server/Flat-bedrock-generator-settings.patch index 052e452869..4ea5971df7 100644 --- a/patches/server/Flat-bedrock-generator-settings.patch +++ b/patches/server/Flat-bedrock-generator-settings.patch @@ -19,24 +19,6 @@ public net.minecraft.world.level.levelgen.SurfaceSystem getOrCreateRandomFactory Co-authored-by: Noah van der Aa <ndvdaa@gmail.com> -diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -+++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java -@@ -0,0 +0,0 @@ public class BuiltInRegistries { - } - - public static void bootStrap() { -+ // Paper start -+ bootStrap(() -> {}); -+ } -+ public static void bootStrap(Runnable runnable) { -+ // Paper end - createContents(); -+ runnable.run(); // Paper - freeze(); - validate(REGISTRY); - } diff --git a/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java b/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/data/worldgen/SurfaceRuleData.java @@ -136,18 +118,13 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/Bootstrap.java +++ b/src/main/java/net/minecraft/server/Bootstrap.java @@ -0,0 +0,0 @@ public class Bootstrap { - EntitySelectorOptions.bootStrap(); - DispenseItemBehavior.bootStrap(); CauldronInteraction.bootStrap(); -- BuiltInRegistries.bootStrap(); -+ // Paper start - register custom flat bedrock -+ BuiltInRegistries.bootStrap(() -> { + // Paper start + BuiltInRegistries.bootStrap(() -> { + net.minecraft.core.Registry.register(net.minecraft.core.registries.BuiltInRegistries.MATERIAL_CONDITION, new net.minecraft.resources.ResourceLocation("paper", "bedrock_condition_source"), net.minecraft.data.worldgen.SurfaceRuleData.PaperBedrockConditionSource.CODEC.codec()); -+ }); -+ // Paper end - Bootstrap.wrapStreams(); - } - // CraftBukkit start - easier than fixing the decompile + io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping + }); + // Paper end diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java diff --git a/patches/server/Paper-Plugins.patch b/patches/server/Paper-Plugins.patch new file mode 100644 index 0000000000..cf53fdf829 --- /dev/null +++ b/patches/server/Paper-Plugins.patch @@ -0,0 +1,6780 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> +Date: Wed, 6 Jul 2022 23:00:31 -0400 +Subject: [PATCH] Paper Plugins + + +diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommand.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommand.java +@@ -0,0 +0,0 @@ import io.papermc.paper.command.subcommands.EntityCommand; + import io.papermc.paper.command.subcommands.HeapDumpCommand; + import io.papermc.paper.command.subcommands.ReloadCommand; + import io.papermc.paper.command.subcommands.VersionCommand; ++import io.papermc.paper.command.subcommands.DumpPluginsCommand; + import it.unimi.dsi.fastutil.Pair; + import java.util.ArrayList; + import java.util.Arrays; +@@ -0,0 +0,0 @@ public final class PaperCommand extends Command { + commands.put(Set.of("entity"), new EntityCommand()); + commands.put(Set.of("reload"), new ReloadCommand()); + commands.put(Set.of("version"), new VersionCommand()); ++ commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); + + return commands.entrySet().stream() + .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue()))) +diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/command/PaperCommands.java ++++ b/src/main/java/io/papermc/paper/command/PaperCommands.java +@@ -0,0 +0,0 @@ public final class PaperCommands { + COMMANDS.forEach((s, command) -> { + server.server.getCommandMap().register(s, "Paper", command); + }); ++ server.server.getCommandMap().register("bukkit", new PaperPluginsCommand()); + } + } +diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command; ++ ++import com.google.common.collect.Lists; ++import io.leangen.geantyref.GenericTypeReflector; ++import io.leangen.geantyref.TypeToken; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.ProviderStatus; ++import io.papermc.paper.plugin.provider.ProviderStatusHolder; ++import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; ++import io.papermc.paper.plugin.provider.type.spigot.SpigotPluginProvider; ++import net.kyori.adventure.text.Component; ++import net.kyori.adventure.text.JoinConfiguration; ++import net.kyori.adventure.text.TextComponent; ++import net.kyori.adventure.text.event.ClickEvent; ++import net.kyori.adventure.text.format.NamedTextColor; ++import net.kyori.adventure.text.format.TextColor; ++import org.bukkit.Bukkit; ++import org.bukkit.command.CommandSender; ++import org.bukkit.command.defaults.BukkitCommand; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++ ++import java.lang.reflect.Type; ++import java.util.ArrayList; ++import java.util.Arrays; ++import java.util.Collections; ++import java.util.List; ++import java.util.TreeMap; ++ ++public class PaperPluginsCommand extends BukkitCommand { ++ ++ private static final TextColor INFO_COLOR = TextColor.color(52, 159, 218); ++ ++ // TODO: LINK? ++ private static final Component SERVER_PLUGIN_INFO = Component.text("ℹ What is a server plugin?", INFO_COLOR) ++ .append(asPlainComponents(""" ++ Server plugins can add new behavior to your server! ++ You can find new plugins on Paper's plugin repository, Hangar. ++ ++ <link to hangar> ++ """)); ++ ++ private static final Component SERVER_INITIALIZER_INFO = Component.text("ℹ What is a server initializer?", INFO_COLOR) ++ .append(asPlainComponents(""" ++ Server initializers are ran before your server ++ starts and are provided by paper plugins. ++ """)); ++ ++ private static final Component LEGACY_PLUGIN_INFO = Component.text("ℹ What is a legacy plugin?", INFO_COLOR) ++ .append(asPlainComponents(""" ++ A legacy plugin is a plugin that was made on ++ very old unsupported versions of the game. ++ ++ It is encouraged that you replace this plugin, ++ as they might not work in the future and may cause ++ performance issues. ++ """)); ++ ++ private static final Component LEGACY_PLUGIN_STAR = Component.text('*', TextColor.color(255, 212, 42)).hoverEvent(LEGACY_PLUGIN_INFO); ++ private static final Component INFO_ICON_START = Component.text("ℹ ", INFO_COLOR); ++ private static final Component PAPER_HEADER = Component.text("Paper Plugins:", TextColor.color(2, 136, 209)); ++ private static final Component BUKKIT_HEADER = Component.text("Bukkit Plugins:", TextColor.color(237, 129, 6)); ++ private static final Component PLUGIN_TICK = Component.text("- ", NamedTextColor.DARK_GRAY); ++ private static final Component PLUGIN_TICK_EMPTY = Component.text(" "); ++ ++ private static final Type JAVA_PLUGIN_PROVIDER_TYPE = new TypeToken<PluginProvider<JavaPlugin>>() {}.getType(); ++ ++ public PaperPluginsCommand() { ++ super("plugins"); ++ this.description = "Gets a list of plugins running on the server"; ++ this.usageMessage = "/plugins"; ++ this.setPermission("bukkit.command.plugins"); ++ this.setAliases(Arrays.asList("pl")); ++ } ++ ++ private static <T> List<Component> formatProviders(TreeMap<String, PluginProvider<T>> plugins) { ++ List<Component> components = new ArrayList<>(plugins.size()); ++ for (PluginProvider<T> entry : plugins.values()) { ++ components.add(formatProvider(entry)); ++ } ++ ++ boolean isFirst = true; ++ List<Component> formattedSublists = new ArrayList<>(); ++ /* ++ Split up the plugin list for each 10 plugins to get size down ++ ++ Plugin List: ++ - Plugin 1, Plugin 2, .... Plugin 10, ++ Plugin 11, Plugin 12 ... Plugin 20, ++ */ ++ for (List<Component> componentSublist : Lists.partition(components, 10)) { ++ Component component = Component.space(); ++ if (isFirst) { ++ component = component.append(PLUGIN_TICK); ++ isFirst = false; ++ } else { ++ component = PLUGIN_TICK_EMPTY; ++ //formattedSublists.add(Component.empty()); // Add an empty line, the auto chat wrapping and this makes it quite jarring. ++ } ++ ++ formattedSublists.add(component.append(Component.join(JoinConfiguration.commas(true), componentSublist))); ++ } ++ ++ return formattedSublists; ++ } ++ ++ private static Component formatProvider(PluginProvider<?> provider) { ++ TextComponent.Builder builder = Component.text(); ++ if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) { ++ builder.append(LEGACY_PLUGIN_STAR); ++ } ++ ++ String name = provider.getMeta().getName(); ++ Component pluginName = Component.text(name, fromStatus(provider)) ++ .clickEvent(ClickEvent.runCommand("/version " + name)); ++ ++ builder.append(pluginName); ++ ++ return builder.build(); ++ } ++ ++ private static Component asPlainComponents(String strings) { ++ net.kyori.adventure.text.TextComponent.Builder builder = Component.text(); ++ for (String string : strings.split("\n")) { ++ builder.append(Component.newline()); ++ builder.append(Component.text(string, NamedTextColor.WHITE)); ++ } ++ ++ return builder.build(); ++ } ++ ++ private static TextColor fromStatus(PluginProvider<?> provider) { ++ if (provider instanceof ProviderStatusHolder statusHolder && statusHolder.getLastProvidedStatus() != null) { ++ ProviderStatus status = statusHolder.getLastProvidedStatus(); ++ ++ // Handle enabled/disabled game plugins ++ if (status == ProviderStatus.INITIALIZED && GenericTypeReflector.isSuperType(JAVA_PLUGIN_PROVIDER_TYPE, provider.getClass())) { ++ Plugin plugin = Bukkit.getPluginManager().getPlugin(provider.getMeta().getName()); ++ // Plugin doesn't exist? Could be due to it being removed. ++ if (plugin == null) { ++ return NamedTextColor.RED; ++ } ++ ++ return plugin.isEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED; ++ } ++ ++ return switch (status) { ++ case INITIALIZED -> NamedTextColor.GREEN; ++ case ERRORED -> NamedTextColor.RED; ++ }; ++ } else if (provider instanceof PaperPluginParent.PaperServerPluginProvider serverPluginProvider && serverPluginProvider.shouldSkipCreation()) { ++ // Paper plugins will be skipped if their provider is skipped due to their initializer failing. ++ // Show them as red ++ return NamedTextColor.RED; ++ } else { ++ // Separated for future logic choice, but this indicated a provider that failed to load due to ++ // dependency issues or what not. ++ return NamedTextColor.RED; ++ } ++ } ++ ++ @Override ++ public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) { ++ if (!this.testPermission(sender)) return true; ++ ++ TreeMap<String, PluginProvider<JavaPlugin>> paperPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); ++ TreeMap<String, PluginProvider<JavaPlugin>> spigotPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); ++ ++ ++ for (PluginProvider<JavaPlugin> provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) { ++ PluginMeta configuration = provider.getMeta(); ++ ++ if (provider instanceof SpigotPluginProvider) { ++ spigotPlugins.put(configuration.getDisplayName(), provider); ++ } else if (provider instanceof PaperPluginParent.PaperServerPluginProvider) { ++ paperPlugins.put(configuration.getDisplayName(), provider); ++ } ++ } ++ ++ Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE); ++ //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs ++ ++ sender.sendMessage(infoMessage); ++ sender.sendMessage(PAPER_HEADER); ++ for (Component component : formatProviders(paperPlugins)) { ++ sender.sendMessage(component); ++ } ++ sender.sendMessage(BUKKIT_HEADER); ++ for (Component component : formatProviders(spigotPlugins)) { ++ sender.sendMessage(component); ++ } ++ ++ return true; ++ } ++ ++ @NotNull ++ @Override ++ public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { ++ return Collections.emptyList(); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.command.subcommands; ++ ++import com.google.gson.JsonArray; ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++import com.google.gson.internal.Streams; ++import com.google.gson.stream.JsonWriter; ++import io.papermc.paper.command.PaperSubcommand; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.entrypoint.classloader.group.LockingClassLoaderGroup; ++import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage; ++import io.papermc.paper.plugin.entrypoint.classloader.group.SimpleListPluginClassLoaderGroup; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; ++import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage; ++import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; ++import io.papermc.paper.plugin.storage.ConfiguredProviderStorage; ++import io.papermc.paper.plugin.storage.ProviderStorage; ++import net.minecraft.server.MinecraftServer; ++import org.bukkit.command.CommandSender; ++import org.bukkit.plugin.Plugin; ++import org.checkerframework.checker.nullness.qual.NonNull; ++import org.checkerframework.framework.qual.DefaultQualifier; ++ ++import java.io.PrintStream; ++import java.io.StringWriter; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.time.LocalDateTime; ++import java.time.format.DateTimeFormatter; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++ ++import static net.kyori.adventure.text.Component.text; ++import static net.kyori.adventure.text.format.NamedTextColor.GREEN; ++import static net.kyori.adventure.text.format.NamedTextColor.RED; ++ ++@DefaultQualifier(NonNull.class) ++public final class DumpPluginsCommand implements PaperSubcommand { ++ @Override ++ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { ++ this.dumpPlugins(sender, args); ++ return true; ++ } ++ ++ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); ++ ++ private void dumpPlugins(final CommandSender sender, final String[] args) { ++ Path parent = Path.of("debug"); ++ Path path = parent.resolve("plugin-info" + FORMATTER.format(LocalDateTime.now()) + ".txt"); ++ try { ++ Files.createDirectories(parent); ++ Files.createFile(path); ++ sender.sendMessage(text("Writing plugin information to " + path, GREEN)); ++ ++ final JsonObject data = this.writeDebug(); ++ ++ StringWriter stringWriter = new StringWriter(); ++ JsonWriter jsonWriter = new JsonWriter(stringWriter); ++ jsonWriter.setIndent(" "); ++ jsonWriter.setLenient(false); ++ Streams.write(data, jsonWriter); ++ ++ try (PrintStream out = new PrintStream(Files.newOutputStream(path), false, StandardCharsets.UTF_8)) { ++ out.print(stringWriter); ++ } ++ sender.sendMessage(text("Successfully written plugin debug information!", GREEN)); ++ } catch (Throwable e) { ++ sender.sendMessage(text("Failed to write plugin information! See the console for more info.", RED)); ++ MinecraftServer.LOGGER.warn("Error occurred while dumping plugin info", e); ++ } ++ } ++ ++ private JsonObject writeDebug() { ++ JsonObject root = new JsonObject(); ++ if (ConfiguredProviderStorage.LEGACY_PLUGIN_LOADING) { ++ root.addProperty("legacy-loading-strategy", true); ++ } ++ ++ this.writeProviders(root); ++ this.writePlugins(root); ++ this.writeClassloaders(root); ++ ++ return root; ++ } ++ ++ private void writeProviders(JsonObject root) { ++ JsonObject rootProviders = new JsonObject(); ++ root.add("providers", rootProviders); ++ ++ for (Map.Entry<Entrypoint<?>, ProviderStorage<?>> entry : LaunchEntryPointHandler.INSTANCE.getStorage().entrySet()) { ++ JsonObject entrypoint = new JsonObject(); ++ ++ JsonArray providers = new JsonArray(); ++ entrypoint.add("providers", providers); ++ ++ List<PluginProvider<Object>> pluginProviders = new ArrayList<>(); ++ for (PluginProvider<?> provider : entry.getValue().getRegisteredProviders()) { ++ JsonObject providerObj = new JsonObject(); ++ providerObj.addProperty("name", provider.getMeta().getName()); ++ providerObj.addProperty("version", provider.getMeta().getVersion()); ++ providerObj.addProperty("dependencies", provider.getMeta().getPluginDependencies().toString()); ++ providerObj.addProperty("soft-dependencies", provider.getMeta().getPluginSoftDependencies().toString()); ++ providerObj.addProperty("load-before", provider.getMeta().getLoadBeforePlugins().toString()); ++ ++ ++ providers.add(providerObj); ++ pluginProviders.add((PluginProvider<Object>) provider); ++ } ++ ++ JsonArray loadOrder = new JsonArray(); ++ entrypoint.add("load-order", loadOrder); ++ ++ ModernPluginLoadingStrategy<Object> modernPluginLoadingStrategy = new ModernPluginLoadingStrategy<>(new ProviderConfiguration<>() { ++ @Override ++ public void applyContext(PluginProvider<Object> provider, DependencyContext dependencyContext) { ++ } ++ ++ @Override ++ public boolean load(PluginProvider<Object> provider, Object provided) { ++ loadOrder.add(provider.getMeta().getName()); ++ return false; ++ } ++ ++ @Override ++ public List<String> requiredDependencies(PluginProvider<Object> provider) { ++ return provider.getMeta().getPluginDependencies(); ++ } ++ ++ @Override ++ public List<String> optionalDependencies(PluginProvider<Object> provider) { ++ return provider.getMeta().getPluginSoftDependencies(); ++ } ++ ++ @Override ++ public List<String> loadBeforeDependencies(PluginProvider<Object> provider) { ++ return provider.getMeta().getLoadBeforePlugins(); ++ } ++ }); ++ modernPluginLoadingStrategy.loadProviders(pluginProviders); ++ ++ rootProviders.add(entry.getKey().getDebugName(), entrypoint); ++ } ++ } ++ ++ private void writePlugins(JsonObject root) { ++ JsonArray rootPlugins = new JsonArray(); ++ root.add("plugins", rootPlugins); ++ ++ for (Plugin plugin : PaperPluginManagerImpl.getInstance().getPlugins()) { ++ rootPlugins.add(plugin.toString()); ++ } ++ } ++ ++ private void writeClassloaders(JsonObject root) { ++ JsonObject classLoadersRoot = new JsonObject(); ++ root.add("classloaders", classLoadersRoot); ++ ++ PaperPluginClassLoaderStorage storage = (PaperPluginClassLoaderStorage) PaperClassLoaderStorage.instance(); ++ classLoadersRoot.addProperty("global", storage.getGlobalGroup().toString()); ++ classLoadersRoot.addProperty("dependency_graph", PaperPluginManagerImpl.getInstance().getInstanceManagerGraph().toString()); ++ ++ JsonArray array = new JsonArray(); ++ classLoadersRoot.add("children", array); ++ for (PluginClassLoaderGroup group : storage.getGroups()) { ++ array.add(this.writeClassloader(group)); ++ } ++ } ++ ++ private JsonObject writeClassloader(PluginClassLoaderGroup group) { ++ JsonObject classLoadersRoot = new JsonObject(); ++ if (group instanceof SimpleListPluginClassLoaderGroup listGroup) { ++ JsonArray array = new JsonArray(); ++ classLoadersRoot.addProperty("main", listGroup.toString()); ++ classLoadersRoot.add("children", array); ++ for (ConfiguredPluginClassLoader innerGroup : listGroup.getClassLoaders()) { ++ array.add(this.writeClassloader(innerGroup)); ++ } ++ ++ } else if (group instanceof LockingClassLoaderGroup locking) { ++ // Unwrap ++ return this.writeClassloader(locking.getParent()); ++ } else { ++ classLoadersRoot.addProperty("raw", group.toString()); ++ } ++ ++ return classLoadersRoot; ++ } ++ ++ private JsonElement writeClassloader(ConfiguredPluginClassLoader innerGroup) { ++ return new JsonPrimitive(innerGroup.toString()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import com.mojang.logging.LogUtils; ++import joptsimple.OptionSet; ++import org.bukkit.configuration.file.YamlConfiguration; ++import org.jetbrains.annotations.NotNull; ++import org.slf4j.Logger; ++ ++import java.io.File; ++import java.nio.file.Path; ++ ++public class PluginInitializerManager { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ private static PluginInitializerManager impl; ++ private final Path pluginDirectory; ++ private final Path updateDirectory; ++ ++ PluginInitializerManager(@NotNull OptionSet minecraftOptionSet) { ++ // We have to load the bukkit configuration inorder to get the update folder location. ++ File configFileLocationBukkit = (File) minecraftOptionSet.valueOf("bukkit-settings"); ++ this.pluginDirectory = ((File) minecraftOptionSet.valueOf("plugins")).toPath(); ++ this.updateDirectory = this.pluginDirectory.resolve(YamlConfiguration.loadConfiguration(configFileLocationBukkit).getString("settings.update-folder", "update")); ++ } ++ ++ public static PluginInitializerManager init(OptionSet optionSet) { ++ impl = new PluginInitializerManager(optionSet); ++ return impl; ++ } ++ ++ public static PluginInitializerManager instance() { ++ return impl; ++ } ++ ++ @NotNull ++ public Path pluginDirectoryPath() { ++ return pluginDirectory; ++ } ++ ++ @NotNull ++ public Path pluginUpdatePath() { ++ return updateDirectory; ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContextImpl.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContextImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContextImpl.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.bootstrap; ++ ++import io.papermc.paper.plugin.PluginInitializerManager; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Path; ++import java.util.logging.Logger; ++ ++public record PluginProviderContextImpl(PluginMeta config, Path dataFolder, ++ Logger logger) implements PluginProviderContext { ++ ++ public static PluginProviderContextImpl of(PluginMeta config, Logger logger) { ++ Path dataFolder = PluginInitializerManager.instance().pluginDirectoryPath().resolve(config.getDisplayName()); ++ ++ return new PluginProviderContextImpl(config, dataFolder, logger); ++ } ++ ++ public static PluginProviderContextImpl of(PluginProvider<?> provider, Path pluginFolder) { ++ Path dataFolder = pluginFolder.resolve(provider.getMeta().getDisplayName()); ++ ++ return new PluginProviderContextImpl(provider.getMeta(), dataFolder, provider.getLogger()); ++ } ++ ++ @Override ++ public @NotNull PluginMeta getConfiguration() { ++ return this.config; ++ } ++ ++ @Override ++ public @NotNull Path getDataDirectory() { ++ return this.dataFolder; ++ } ++ ++ @Override ++ public @NotNull Logger getLogger() { ++ return this.logger; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/Entrypoint.java b/src/main/java/io/papermc/paper/plugin/entrypoint/Entrypoint.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/Entrypoint.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint; ++ ++import io.papermc.paper.plugin.bootstrap.PluginBootstrap; ++import org.bukkit.plugin.java.JavaPlugin; ++ ++/** ++ * Used to mark a certain place that {@link EntrypointHandler} will register {@link io.papermc.paper.plugin.provider.PluginProvider} under. ++ * Used for loading only certain providers at a certain time. ++ * @param <T> provider type ++ */ ++public final class Entrypoint<T> { ++ ++ public static final Entrypoint<PluginBootstrap> BOOTSTRAPPER = new Entrypoint<>("bootstrapper"); ++ public static final Entrypoint<JavaPlugin> PLUGIN = new Entrypoint<>("plugin"); ++ ++ private final String debugName; ++ ++ private Entrypoint(String debugName) { ++ this.debugName = debugName; ++ } ++ ++ public String getDebugName() { ++ return debugName; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/EntrypointHandler.java b/src/main/java/io/papermc/paper/plugin/entrypoint/EntrypointHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/EntrypointHandler.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint; ++ ++import io.papermc.paper.plugin.provider.PluginProvider; ++ ++/** ++ * Represents a register that will register providers at a certain {@link Entrypoint}, ++ * where then when the given {@link Entrypoint} is registered those will be loaded. ++ */ ++public interface EntrypointHandler { ++ ++ <T> void register(Entrypoint<T> entrypoint, PluginProvider<T> provider); ++ ++ void enter(Entrypoint<?> entrypoint); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint; ++ ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.storage.BootstrapProviderStorage; ++import io.papermc.paper.plugin.storage.ProviderStorage; ++import io.papermc.paper.plugin.storage.ServerPluginProviderStorage; ++import org.jetbrains.annotations.ApiStatus; ++ ++import java.util.HashMap; ++import java.util.Map; ++ ++/** ++ * Used by the server to register/load plugin bootstrappers and plugins. ++ */ ++public class LaunchEntryPointHandler implements EntrypointHandler { ++ ++ public static final LaunchEntryPointHandler INSTANCE = new LaunchEntryPointHandler(); ++ private final Map<Entrypoint<?>, ProviderStorage<?>> storage = new HashMap<>(); ++ ++ LaunchEntryPointHandler() { ++ this.storage.put(Entrypoint.BOOTSTRAPPER, new BootstrapProviderStorage()); ++ this.storage.put(Entrypoint.PLUGIN, new ServerPluginProviderStorage()); ++ } ++ ++ // Utility ++ public static void enterBootstrappers() { ++ LaunchEntryPointHandler.INSTANCE.enter(Entrypoint.BOOTSTRAPPER); ++ } ++ ++ @Override ++ public void enter(Entrypoint<?> entrypoint) { ++ ProviderStorage<?> storage = this.storage.get(entrypoint); ++ if (storage == null) { ++ throw new IllegalArgumentException("No storage registered for entrypoint %s.".formatted(entrypoint)); ++ } ++ ++ storage.enter(); ++ } ++ ++ @Override ++ public <T> void register(Entrypoint<T> entrypoint, PluginProvider<T> provider) { ++ ProviderStorage<T> storage = this.get(entrypoint); ++ if (storage == null) { ++ throw new IllegalArgumentException("No storage registered for entrypoint %s.".formatted(entrypoint)); ++ } ++ ++ storage.register(provider); ++ } ++ ++ @SuppressWarnings("unchecked") ++ public <T> ProviderStorage<T> get(Entrypoint<T> entrypoint) { ++ return (ProviderStorage<T>) this.storage.get(entrypoint); ++ } ++ ++ // Debug only ++ @ApiStatus.Internal ++ public Map<Entrypoint<?>, ProviderStorage<?>> getStorage() { ++ return storage; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/ClassloaderBytecodeModifier.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/ClassloaderBytecodeModifier.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/ClassloaderBytecodeModifier.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import net.kyori.adventure.util.Services; ++import org.jetbrains.annotations.ApiStatus; ++ ++@ApiStatus.Internal ++public interface ClassloaderBytecodeModifier { ++ ++ static ClassloaderBytecodeModifier bytecodeModifier() { ++ return Provider.INSTANCE; ++ } ++ ++ byte[] modify(PluginMeta config, byte[] bytecode); ++ ++ class Provider { ++ ++ private static final ClassloaderBytecodeModifier INSTANCE = Services.service(ClassloaderBytecodeModifier.class).orElseThrow(); ++ ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++ ++// Stub, implement in future. ++public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModifier { ++ ++ @Override ++ public byte[] modify(PluginMeta configuration, byte[] bytecode) { ++ return bytecode; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage; ++import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage; ++import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.File; ++import java.io.IOException; ++import java.net.URL; ++import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.Collections; ++import java.util.Enumeration; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.concurrent.ConcurrentHashMap; ++import java.util.jar.JarFile; ++import java.util.logging.Logger; ++ ++/** ++ * This is similar to a {@link org.bukkit.plugin.java.PluginClassLoader} but is completely kept hidden from the api. ++ * This is only used with Paper plugins. ++ * ++ * @see PaperPluginClassLoaderStorage ++ */ ++public class PaperPluginClassLoader extends PaperSimplePluginClassLoader implements ConfiguredPluginClassLoader { ++ ++ static { ++ registerAsParallelCapable(); ++ } ++ ++ private final ClassLoader libraryLoader; ++ private final Set<String> seenIllegalAccess = Collections.newSetFromMap(new ConcurrentHashMap<>()); ++ private final Logger logger; ++ @Nullable ++ private JavaPlugin loadedJavaPlugin; ++ @Nullable ++ private PluginClassLoaderGroup group; ++ ++ public PaperPluginClassLoader(Logger logger, Path source, JarFile file, PaperPluginMeta configuration, ClassLoader parentLoader, ClassLoader libraryLoader) throws IOException { ++ super(source, file, configuration, parentLoader); ++ this.libraryLoader = libraryLoader; ++ ++ this.logger = logger; ++ if (this.configuration.hasOpenClassloader()) { ++ this.group = PaperClassLoaderStorage.instance().registerOpenGroup(this); ++ } ++ } ++ ++ public void refreshClassloaderDependencyTree(DependencyContext dependencyContext) { ++ if (this.configuration.hasOpenClassloader()) { ++ return; ++ } ++ if (this.group != null) { ++ // We need to unregister the classloader inorder to allow for dependencies ++ // to be recalculated ++ PaperClassLoaderStorage.instance().unregisterClassloader(this); ++ } ++ ++ this.group = PaperClassLoaderStorage.instance().registerAccessBackedGroup(this, (classLoader) -> { ++ return dependencyContext.isTransitiveDependency(PaperPluginClassLoader.this.configuration, classLoader.getConfiguration()); ++ }); ++ } ++ ++ @Override ++ public URL getResource(String name) { ++ URL resource = findResource(name); ++ if (resource == null && this.libraryLoader != null) { ++ return this.libraryLoader.getResource(name); ++ } ++ return resource; ++ } ++ ++ @Override ++ public Enumeration<URL> getResources(String name) throws IOException { ++ List<URL> resources = new ArrayList<>(); ++ this.addEnumeration(resources, this.findResources(name)); ++ if (this.libraryLoader != null) { ++ addEnumeration(resources, this.libraryLoader.getResources(name)); ++ } ++ return Collections.enumeration(resources); ++ } ++ ++ private <T> void addEnumeration(List<T> list, Enumeration<T> enumeration) { ++ while (enumeration.hasMoreElements()) { ++ list.add(enumeration.nextElement()); ++ } ++ } ++ ++ @Override ++ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { ++ return this.loadClass(name, resolve, true, true); ++ } ++ ++ @Override ++ public PluginMeta getConfiguration() { ++ return this.configuration; ++ } ++ ++ @Override ++ public Class<?> loadClass(@NotNull String name, boolean resolve, boolean checkGroup, boolean checkLibraries) throws ClassNotFoundException { ++ try { ++ Class<?> result = super.loadClass(name, resolve); ++ ++ // SPIGOT-6749: Library classes will appear in the above, but we don't want to return them to other plugins ++ if (checkGroup || result.getClassLoader() == this) { ++ return result; ++ } ++ } catch (ClassNotFoundException ignored) { ++ } ++ ++ if (checkLibraries) { ++ try { ++ return this.libraryLoader.loadClass(name); ++ } catch (ClassNotFoundException ignored) { ++ } ++ } ++ ++ if (checkGroup) { ++ // This ignores the libraries of other plugins, unless they are transitive dependencies. ++ if (this.group == null) { ++ throw new IllegalStateException("Tried to resolve class while group was not yet initialized"); ++ } ++ ++ Class<?> clazz = this.group.getClassByName(name, resolve, this); ++ if (clazz != null) { ++ return clazz; ++ } ++ } ++ ++ throw new ClassNotFoundException(name); ++ } ++ ++ @Override ++ public void init(JavaPlugin plugin) { ++ PluginMeta config = this.configuration; ++ PluginDescriptionFile pluginDescriptionFile = new PluginDescriptionFile( ++ config.getName(), ++ config.getDisplayName(), ++ config.getProvidedPlugins(), ++ config.getMainClass(), ++ "", // Classloader load order api ++ List.of(), // Dependencies ++ List.of(), // Soft Depends ++ List.of(), // Load Before ++ config.getVersion(), ++ Map.of(), // Commands, we use a separate system ++ config.getDescription(), ++ config.getAuthors(), ++ config.getContributors(), ++ config.getWebsite(), ++ config.getLoggerPrefix(), ++ config.getLoadOrder(), ++ config.getPermissions(), ++ config.getPermissionDefault(), ++ Set.of(), // Aware api ++ config.getAPIVersion(), ++ List.of() // Libraries ++ ); ++ ++ File dataFolder = new File(Bukkit.getPluginsFolder(), pluginDescriptionFile.getName()); ++ ++ plugin.init(Bukkit.getServer(), pluginDescriptionFile, dataFolder, this.source.toFile(), this, config); ++ plugin.logger = this.logger; ++ ++ this.loadedJavaPlugin = plugin; ++ } ++ ++ @Nullable ++ public JavaPlugin getLoadedJavaPlugin() { ++ return this.loadedJavaPlugin; ++ } ++ ++ @Override ++ public String toString() { ++ return "PaperPluginClassLoader{" + ++ "libraryLoader=" + this.libraryLoader + ++ ", seenIllegalAccess=" + this.seenIllegalAccess + ++ ", loadedJavaPlugin=" + this.loadedJavaPlugin + ++ ", group=" + this.group + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperSimplePluginClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperSimplePluginClassLoader.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperSimplePluginClassLoader.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader; ++ ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import io.papermc.paper.plugin.util.NamespaceChecker; ++import org.jetbrains.annotations.ApiStatus; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.net.URL; ++import java.net.URLClassLoader; ++import java.nio.file.Path; ++import java.security.CodeSigner; ++import java.security.CodeSource; ++import java.util.Enumeration; ++import java.util.jar.JarEntry; ++import java.util.jar.JarFile; ++import java.util.jar.Manifest; ++ ++/** ++ * Represents a simple classloader used for paper plugin bootstrappers. ++ */ ++@ApiStatus.Internal ++public class PaperSimplePluginClassLoader extends URLClassLoader { ++ ++ static { ++ ClassLoader.registerAsParallelCapable(); ++ } ++ ++ protected final PaperPluginMeta configuration; ++ protected final Path source; ++ protected final Manifest jarManifest; ++ protected final URL jarUrl; ++ protected final JarFile jar; ++ ++ public PaperSimplePluginClassLoader(Path source, JarFile file, PaperPluginMeta configuration, ClassLoader parentLoader) throws IOException { ++ super(source.getFileName().toString(), new URL[]{source.toUri().toURL()}, parentLoader); ++ ++ this.source = source; ++ this.jarManifest = file.getManifest(); ++ this.jarUrl = source.toUri().toURL(); ++ this.configuration = configuration; ++ this.jar = file; ++ } ++ ++ @Override ++ public URL getResource(String name) { ++ return this.findResource(name); ++ } ++ ++ @Override ++ public Enumeration<URL> getResources(String name) throws IOException { ++ return this.findResources(name); ++ } ++ ++ // Bytecode modification supported loader ++ @Override ++ protected Class<?> findClass(String name) throws ClassNotFoundException { ++ NamespaceChecker.validateNameSpaceForClassloading(name); ++ ++ // See UrlClassLoader#findClass(String) ++ String path = name.replace('.', '/').concat(".class"); ++ JarEntry entry = this.jar.getJarEntry(path); ++ if (entry == null) { ++ throw new ClassNotFoundException(); ++ } ++ ++ // See URLClassLoader#defineClass(String, Resource) ++ byte[] classBytes; ++ ++ try (InputStream is = this.jar.getInputStream(entry)) { ++ classBytes = is.readAllBytes(); ++ } catch (IOException ex) { ++ throw new ClassNotFoundException(name, ex); ++ } ++ ++ classBytes = ClassloaderBytecodeModifier.bytecodeModifier().modify(this.configuration, classBytes); ++ ++ int dot = name.lastIndexOf('.'); ++ if (dot != -1) { ++ String pkgName = name.substring(0, dot); ++ // Get defined package does not correctly handle sealed packages. ++ if (this.getDefinedPackage(pkgName) == null) { ++ try { ++ if (this.jarManifest != null) { ++ this.definePackage(pkgName, this.jarManifest, this.jarUrl); ++ } else { ++ this.definePackage(pkgName, null, null, null, null, null, null, null); ++ } ++ } catch (IllegalArgumentException ex) { ++ // parallel-capable class loaders: re-verify in case of a ++ // race condition ++ if (this.getDefinedPackage(pkgName) == null) { ++ // Should never happen ++ throw new IllegalStateException("Cannot find package " + pkgName); ++ } ++ } ++ } ++ } ++ ++ CodeSigner[] signers = entry.getCodeSigners(); ++ CodeSource source = new CodeSource(this.jarUrl, signers); ++ ++ return this.defineClass(name, classBytes, 0, classBytes.length, source); ++ } ++ ++ @Override ++ public String toString() { ++ return "PaperSimplePluginClassLoader{" + ++ "configuration=" + this.configuration + ++ ", source=" + this.source + ++ ", jarManifest=" + this.jarManifest + ++ ", jarUrl=" + this.jarUrl + ++ ", jar=" + this.jar + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/DependencyBasedPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/DependencyBasedPluginClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/DependencyBasedPluginClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import org.jetbrains.annotations.ApiStatus; ++ ++import java.util.ArrayList; ++ ++@ApiStatus.Internal ++public class DependencyBasedPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup { ++ ++ private final GlobalPluginClassLoaderGroup globalPluginClassLoaderGroup; ++ private final ClassLoaderAccess access; ++ ++ public DependencyBasedPluginClassLoaderGroup(GlobalPluginClassLoaderGroup globalPluginClassLoaderGroup, ClassLoaderAccess access) { ++ super(new ArrayList<>()); ++ this.access = access; ++ this.globalPluginClassLoaderGroup = globalPluginClassLoaderGroup; ++ } ++ ++ /** ++ * This will refresh the dependencies of the current classloader. ++ */ ++ public void populateDependencies() { ++ this.classloaders.clear(); ++ for (ConfiguredPluginClassLoader configuredPluginClassLoader : this.globalPluginClassLoaderGroup.getClassLoaders()) { ++ if (this.access.canAccess(configuredPluginClassLoader)) { ++ this.classloaders.add(configuredPluginClassLoader); ++ } ++ } ++ ++ } ++ ++ @Override ++ public ClassLoaderAccess getAccess() { ++ return this.access; ++ } ++ ++ @Override ++ public String toString() { ++ return "DependencyBasedPluginClassLoaderGroup{" + ++ "globalPluginClassLoaderGroup=" + this.globalPluginClassLoaderGroup + ++ ", access=" + this.access + ++ ", classloaders=" + this.classloaders + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/GlobalPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/GlobalPluginClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/GlobalPluginClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess; ++import org.jetbrains.annotations.ApiStatus; ++ ++@ApiStatus.Internal ++public class GlobalPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup { ++ ++ @Override ++ public ClassLoaderAccess getAccess() { ++ return (v) -> true; ++ } ++ ++ @Override ++ public String toString() { ++ return super.toString(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/LockingClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/LockingClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/LockingClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.HashMap; ++import java.util.Map; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.concurrent.locks.ReentrantReadWriteLock; ++ ++@ApiStatus.Internal ++public class LockingClassLoaderGroup implements PluginClassLoaderGroup { ++ ++ private final PluginClassLoaderGroup parent; ++ private final Map<String, ClassLockEntry> classLoadLock = new HashMap<>(); ++ ++ public LockingClassLoaderGroup(PluginClassLoaderGroup parent) { ++ this.parent = parent; ++ } ++ ++ @Override ++ public @Nullable Class<?> getClassByName(String name, boolean resolve, ConfiguredPluginClassLoader requester) { ++ // make MT safe ++ ClassLockEntry lock; ++ synchronized (this.classLoadLock) { ++ lock = this.classLoadLock.computeIfAbsent(name, (x) -> new ClassLockEntry(new AtomicInteger(0), new java.util.concurrent.locks.ReentrantReadWriteLock())); ++ lock.count.incrementAndGet(); ++ } ++ lock.reentrantReadWriteLock.writeLock().lock(); ++ try { ++ return parent.getClassByName(name, resolve, requester); ++ } finally { ++ synchronized (this.classLoadLock) { ++ lock.reentrantReadWriteLock.writeLock().unlock(); ++ if (lock.count.get() == 1) { ++ this.classLoadLock.remove(name); ++ } else { ++ lock.count.decrementAndGet(); ++ } ++ } ++ } ++ } ++ ++ @Override ++ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ this.parent.remove(configuredPluginClassLoader); ++ } ++ ++ @Override ++ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ this.parent.add(configuredPluginClassLoader); ++ } ++ ++ @Override ++ public ClassLoaderAccess getAccess() { ++ return this.parent.getAccess(); ++ } ++ ++ public PluginClassLoaderGroup getParent() { ++ return parent; ++ } ++ ++ record ClassLockEntry(AtomicInteger count, ReentrantReadWriteLock reentrantReadWriteLock) { ++ } ++ ++ @Override ++ public String toString() { ++ return "LockingClassLoaderGroup{" + ++ "parent=" + this.parent + ++ ", classLoadLock=" + this.classLoadLock + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/PaperPluginClassLoaderStorage.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/PaperPluginClassLoaderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/PaperPluginClassLoaderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage; ++import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.java.PluginClassLoader; ++import org.jetbrains.annotations.ApiStatus; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++/** ++ * This is used for connecting multiple classloaders. ++ */ ++public final class PaperPluginClassLoaderStorage implements PaperClassLoaderStorage { ++ ++ private final GlobalPluginClassLoaderGroup globalGroup = new GlobalPluginClassLoaderGroup(); ++ private final List<PluginClassLoaderGroup> groups = new CopyOnWriteArrayList<>(); ++ ++ public PaperPluginClassLoaderStorage() { ++ this.groups.add(this.globalGroup); ++ } ++ ++ @Override ++ public PluginClassLoaderGroup registerSpigotGroup(PluginClassLoader pluginClassLoader) { ++ return this.registerGroup(pluginClassLoader, new SpigotPluginClassLoaderGroup(this.globalGroup, (library) -> { ++ return Bukkit.getServer().getPluginManager().isTransitiveDependency(pluginClassLoader.getConfiguration(), library.getConfiguration()); ++ })); ++ } ++ ++ @Override ++ public PluginClassLoaderGroup registerOpenGroup(ConfiguredPluginClassLoader classLoader) { ++ return this.registerGroup(classLoader, this.globalGroup); ++ } ++ ++ @Override ++ public PluginClassLoaderGroup registerAccessBackedGroup(ConfiguredPluginClassLoader classLoader, ClassLoaderAccess access) { ++ List<ConfiguredPluginClassLoader> allowedLoaders = new ArrayList<>(); ++ for (ConfiguredPluginClassLoader configuredPluginClassLoader : this.globalGroup.getClassLoaders()) { ++ if (access.canAccess(configuredPluginClassLoader)) { ++ allowedLoaders.add(configuredPluginClassLoader); ++ } ++ } ++ ++ return this.registerGroup(classLoader, new StaticPluginClassLoaderGroup(allowedLoaders, access)); ++ } ++ ++ private PluginClassLoaderGroup registerGroup(ConfiguredPluginClassLoader classLoader, PluginClassLoaderGroup group) { ++ // Now add this classloader to any groups that allows it (includes global) ++ for (PluginClassLoaderGroup loaderGroup : this.groups) { ++ if (loaderGroup.getAccess().canAccess(classLoader)) { ++ loaderGroup.add(classLoader); ++ } ++ } ++ ++ group = new LockingClassLoaderGroup(group); ++ this.groups.add(group); ++ return group; ++ } ++ ++ @Override ++ public void unregisterClassloader(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ this.globalGroup.remove(configuredPluginClassLoader); ++ for (PluginClassLoaderGroup group : this.groups) { ++ group.remove(configuredPluginClassLoader); ++ } ++ } ++ ++ @Override ++ public boolean registerUnsafePlugin(ConfiguredPluginClassLoader pluginLoader) { ++ if (this.globalGroup.getClassLoaders().contains(pluginLoader)) { ++ return false; ++ } else { ++ this.globalGroup.getClassLoaders().add(pluginLoader); ++ return true; ++ } ++ } ++ ++ // Debug only ++ @ApiStatus.Internal ++ public GlobalPluginClassLoaderGroup getGlobalGroup() { ++ return this.globalGroup; ++ } ++ ++ // Debug only ++ @ApiStatus.Internal ++ public List<PluginClassLoaderGroup> getGroups() { ++ return this.groups; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SimpleListPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SimpleListPluginClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SimpleListPluginClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.List; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++@ApiStatus.Internal ++public abstract class SimpleListPluginClassLoaderGroup implements PluginClassLoaderGroup { ++ ++ private static final boolean DISABLE_CLASS_PRIORITIZATION = Boolean.getBoolean("Paper.DisableClassPrioritization"); ++ ++ protected final List<ConfiguredPluginClassLoader> classloaders; ++ ++ protected SimpleListPluginClassLoaderGroup() { ++ this(new CopyOnWriteArrayList<>()); ++ } ++ ++ protected SimpleListPluginClassLoaderGroup(List<ConfiguredPluginClassLoader> classloaders) { ++ this.classloaders = classloaders; ++ } ++ ++ @Override ++ public @Nullable Class<?> getClassByName(String name, boolean resolve, ConfiguredPluginClassLoader requester) { ++ if (!DISABLE_CLASS_PRIORITIZATION) { ++ try { ++ return this.lookupClass(name, false, requester); // First check the requester ++ } catch (ClassNotFoundException ignored) { ++ } ++ } ++ ++ for (ConfiguredPluginClassLoader loader : this.classloaders) { ++ try { ++ return this.lookupClass(name, resolve, loader); ++ } catch (ClassNotFoundException ignored) { ++ } ++ } ++ ++ return null; ++ } ++ ++ protected Class<?> lookupClass(String name, boolean resolve, ConfiguredPluginClassLoader current) throws ClassNotFoundException { ++ return current.loadClass(name, resolve, false, true); ++ } ++ ++ @Override ++ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ this.classloaders.remove(configuredPluginClassLoader); ++ } ++ ++ @Override ++ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ this.classloaders.add(configuredPluginClassLoader); ++ } ++ ++ public List<ConfiguredPluginClassLoader> getClassLoaders() { ++ return classloaders; ++ } ++ ++ @Override ++ public String toString() { ++ return "SimpleListPluginClassLoaderGroup{" + ++ "classloaders=" + this.classloaders + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SingletonPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SingletonPluginClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SingletonPluginClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.Nullable; ++ ++@ApiStatus.Internal ++public class SingletonPluginClassLoaderGroup implements PluginClassLoaderGroup { ++ ++ private final ConfiguredPluginClassLoader configuredPluginClassLoader; ++ private final Access access; ++ ++ public SingletonPluginClassLoaderGroup(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ this.configuredPluginClassLoader = configuredPluginClassLoader; ++ this.access = new Access(); ++ } ++ ++ @Override ++ public @Nullable Class<?> getClassByName(String name, boolean resolve, ConfiguredPluginClassLoader requester) { ++ try { ++ return this.configuredPluginClassLoader.loadClass(name, resolve, false, true); ++ } catch (ClassNotFoundException ignored) { ++ } ++ ++ return null; ++ } ++ ++ @Override ++ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ } ++ ++ @Override ++ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ } ++ ++ @Override ++ public ClassLoaderAccess getAccess() { ++ return this.access; ++ } ++ ++ @ApiStatus.Internal ++ private class Access implements ClassLoaderAccess { ++ ++ @Override ++ public boolean canAccess(ConfiguredPluginClassLoader classLoader) { ++ return SingletonPluginClassLoaderGroup.this.configuredPluginClassLoader == classLoader; ++ } ++ ++ } ++ ++ @Override ++ public String toString() { ++ return "SingletonPluginClassLoaderGroup{" + ++ "configuredPluginClassLoader=" + this.configuredPluginClassLoader + ++ ", access=" + this.access + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SpigotPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SpigotPluginClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SpigotPluginClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import org.jetbrains.annotations.ApiStatus; ++ ++import java.util.function.Predicate; ++ ++/** ++ * Spigot classloaders have the ability to see everything. ++ * However, libraries are ONLY shared depending on their dependencies. ++ */ ++@ApiStatus.Internal ++public class SpigotPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup { ++ ++ private final Predicate<ConfiguredPluginClassLoader> libraryClassloaderPredicate; ++ ++ public SpigotPluginClassLoaderGroup(GlobalPluginClassLoaderGroup globalPluginClassLoaderGroup, Predicate<ConfiguredPluginClassLoader> libraryClassloaderPredicate) { ++ super(globalPluginClassLoaderGroup.getClassLoaders()); ++ this.libraryClassloaderPredicate = libraryClassloaderPredicate; ++ } ++ ++ // Mirrors global list ++ @Override ++ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ } ++ ++ @Override ++ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ } ++ ++ @Override ++ protected Class<?> lookupClass(String name, boolean resolve, ConfiguredPluginClassLoader current) throws ClassNotFoundException { ++ return current.loadClass(name, resolve, false, this.libraryClassloaderPredicate.test(current)); ++ } ++ ++ @Override ++ public ClassLoaderAccess getAccess() { ++ return v -> true; ++ } ++ ++ @Override ++ public String toString() { ++ return "SpigotPluginClassLoaderGroup{" + ++ "libraryClassloaderPredicate=" + this.libraryClassloaderPredicate + ++ ", classloaders=" + this.classloaders + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/StaticPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/StaticPluginClassLoaderGroup.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/StaticPluginClassLoaderGroup.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.classloader.group; ++ ++import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import org.jetbrains.annotations.ApiStatus; ++ ++import java.util.List; ++ ++@ApiStatus.Internal ++public class StaticPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup { ++ ++ private final ClassLoaderAccess access; ++ ++ public StaticPluginClassLoaderGroup(List<ConfiguredPluginClassLoader> classloaders, ClassLoaderAccess access) { ++ super(classloaders); ++ this.access = access; ++ } ++ ++ @Override ++ public ClassLoaderAccess getAccess() { ++ return this.access; ++ } ++ ++ @Override ++ public String toString() { ++ return "StaticPluginClassLoaderGroup{" + ++ "access=" + this.access + ++ ", classloaders=" + this.classloaders + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyContextHolder.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyContextHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyContextHolder.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.dependency; ++ ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++ ++public interface DependencyContextHolder { ++ ++ void setContext(DependencyContext context); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.dependency; ++ ++import com.google.common.graph.MutableGraph; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++@SuppressWarnings("UnstableApiUsage") ++public class DependencyUtil { ++ ++ @NotNull ++ public static MutableGraph<String> buildDependencyGraph(@NotNull MutableGraph<String> dependencyGraph, @NotNull PluginMeta configuration) { ++ List<String> dependencies = new ArrayList<>(); ++ dependencies.addAll(configuration.getPluginDependencies()); ++ dependencies.addAll(configuration.getPluginSoftDependencies()); ++ ++ return buildDependencyGraph(dependencyGraph, configuration.getName(), dependencies, configuration.getLoadBeforePlugins()); ++ } ++ ++ @NotNull ++ public static MutableGraph<String> buildDependencyGraph(@NotNull MutableGraph<String> dependencyGraph, String identifier, @NotNull Iterable<String> depends, @NotNull Iterable<String> loadBefore) { ++ for (String dependency : depends) { ++ dependencyGraph.putEdge(identifier, dependency); ++ } ++ ++ for (String loadBeforeTarget : loadBefore) { ++ dependencyGraph.putEdge(loadBeforeTarget, identifier); ++ } ++ ++ dependencyGraph.addNode(identifier); // Make sure dependencies at least have a node ++ return dependencyGraph; ++ } ++ ++ // This adds a provided plugin to another plugin, basically making it seem like a "dependency" ++ // in order to have plugins that need the provided plugin to load after the specified plugin name ++ @NotNull ++ public static MutableGraph<String> addProvidedPlugin(@NotNull MutableGraph<String> dependencyGraph, @NotNull String pluginName, @NotNull String providedName) { ++ dependencyGraph.putEdge(pluginName, providedName); ++ ++ return dependencyGraph; ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.dependency; ++ ++import com.google.common.graph.Graph; ++import com.google.common.graph.Graphs; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++ ++import java.util.Set; ++ ++@SuppressWarnings("UnstableApiUsage") ++public class GraphDependencyContext implements DependencyContext { ++ ++ private final Graph<String> dependencyGraph; ++ ++ public GraphDependencyContext(Graph<String> dependencyGraph) { ++ this.dependencyGraph = dependencyGraph; ++ } ++ ++ @Override ++ public boolean isTransitiveDependency(PluginMeta plugin, PluginMeta depend) { ++ String pluginIdentifier = plugin.getName(); ++ ++ if (this.dependencyGraph.nodes().contains(pluginIdentifier)) { ++ Set<String> reachableNodes = Graphs.reachableNodes(this.dependencyGraph, pluginIdentifier); ++ if (reachableNodes.contains(depend.getName())) { ++ return true; ++ } ++ for (String provided : depend.getProvidedPlugins()) { ++ if (reachableNodes.contains(provided)) { ++ return true; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ @Override ++ public boolean hasDependency(String pluginIdentifier) { ++ return this.dependencyGraph.nodes().contains(pluginIdentifier); ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java +@@ -0,0 +0,0 @@ ++/* ++ * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. ++ * ++ * JGraphT : a free Java graph-theory library ++ * ++ * See the CONTRIBUTORS.md file distributed with this work for additional ++ * information regarding copyright ownership. ++ * ++ * This program and the accompanying materials are made available under the ++ * terms of the Eclipse Public License 2.0 which is available at ++ * http://www.eclipse.org/legal/epl-2.0, or the ++ * GNU Lesser General Public License v2.1 or later ++ * which is available at ++ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. ++ * ++ * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later ++ */ ++ ++// MODIFICATIONS: ++// - Modified to use a guava graph directly ++ ++package io.papermc.paper.plugin.entrypoint.strategy; ++ ++import com.google.common.base.Preconditions; ++import com.google.common.graph.Graph; ++import com.google.common.graph.GraphBuilder; ++import com.google.common.graph.MutableGraph; ++import com.mojang.datafixers.util.Pair; ++ ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.function.Consumer; ++ ++/** ++ * Find all simple cycles of a directed graph using the Johnson's algorithm. ++ * ++ * <p> ++ * See:<br> ++ * D.B.Johnson, Finding all the elementary circuits of a directed graph, SIAM J. Comput., 4 (1975), ++ * pp. 77-84. ++ * ++ * @param <V> the vertex type. ++ * ++ * @author Nikolay Ognyanov ++ */ ++public class JohnsonSimpleCycles<V> ++{ ++ // The graph. ++ private Graph<V> graph; ++ ++ // The main state of the algorithm. ++ private Consumer<List<V>> cycleConsumer = null; ++ private V[] iToV = null; ++ private Map<V, Integer> vToI = null; ++ private Set<V> blocked = null; ++ private Map<V, Set<V>> bSets = null; ++ private ArrayDeque<V> stack = null; ++ ++ // The state of the embedded Tarjan SCC algorithm. ++ private List<Set<V>> foundSCCs = null; ++ private int index = 0; ++ private Map<V, Integer> vIndex = null; ++ private Map<V, Integer> vLowlink = null; ++ private ArrayDeque<V> path = null; ++ private Set<V> pathSet = null; ++ ++ /** ++ * Create a simple cycle finder for the specified graph. ++ * ++ * @param graph - the DirectedGraph in which to find cycles. ++ * ++ * @throws IllegalArgumentException if the graph argument is <code> ++ * null</code>. ++ */ ++ public JohnsonSimpleCycles(Graph<V> graph) ++ { ++ Preconditions.checkState(graph.isDirected(), "Graph must be directed"); ++ this.graph = graph; ++ } ++ ++ /** ++ * Find the simple cycles of the graph. ++ * ++ * @return The list of all simple cycles. Possibly empty but never <code>null</code>. ++ */ ++ public List<List<V>> findSimpleCycles() ++ { ++ List<List<V>> result = new ArrayList<>(); ++ findSimpleCycles(result::add); ++ return result; ++ } ++ ++ /** ++ * Find the simple cycles of the graph. ++ * ++ * @param consumer Consumer that will be called with each cycle found. ++ */ ++ public void findSimpleCycles(Consumer<List<V>> consumer) ++ { ++ if (graph == null) { ++ throw new IllegalArgumentException("Null graph."); ++ } ++ initState(consumer); ++ ++ int startIndex = 0; ++ int size = graph.nodes().size(); ++ while (startIndex < size) { ++ Pair<Graph<V>, Integer> minSCCGResult = findMinSCSG(startIndex); ++ if (minSCCGResult != null) { ++ startIndex = minSCCGResult.getSecond(); ++ Graph<V> scg = minSCCGResult.getFirst(); ++ V startV = toV(startIndex); ++ for (V v : scg.successors(startV)) { ++ blocked.remove(v); ++ getBSet(v).clear(); ++ } ++ findCyclesInSCG(startIndex, startIndex, scg); ++ startIndex++; ++ } else { ++ break; ++ } ++ } ++ ++ clearState(); ++ } ++ ++ private Pair<Graph<V>, Integer> findMinSCSG(int startIndex) ++ { ++ /* ++ * Per Johnson : "adjacency structure of strong component $K$ with least vertex in subgraph ++ * of $G$ induced by $(s, s + 1, n)$". Or in contemporary terms: the strongly connected ++ * component of the subgraph induced by $(v_1, \dotso ,v_n)$ which contains the minimum ++ * (among those SCCs) vertex index. We return that index together with the graph. ++ */ ++ initMinSCGState(); ++ ++ List<Set<V>> foundSCCs = findSCCS(startIndex); ++ ++ // find the SCC with the minimum index ++ int minIndexFound = Integer.MAX_VALUE; ++ Set<V> minSCC = null; ++ for (Set<V> scc : foundSCCs) { ++ for (V v : scc) { ++ int t = toI(v); ++ if (t < minIndexFound) { ++ minIndexFound = t; ++ minSCC = scc; ++ } ++ } ++ } ++ if (minSCC == null) { ++ return null; ++ } ++ ++ // build a graph for the SCC found ++ MutableGraph<V> dependencyGraph = GraphBuilder.directed().allowsSelfLoops(true).build(); ++ ++ for (V v : minSCC) { ++ for (V w : minSCC) { ++ if (graph.hasEdgeConnecting(v, w)) { ++ dependencyGraph.putEdge(v, w); ++ } ++ } ++ } ++ ++ Pair<Graph<V>, Integer> result = Pair.of(dependencyGraph, minIndexFound); ++ clearMinSCCState(); ++ return result; ++ } ++ ++ private List<Set<V>> findSCCS(int startIndex) ++ { ++ // Find SCCs in the subgraph induced ++ // by vertices startIndex and beyond. ++ // A call to StrongConnectivityAlgorithm ++ // would be too expensive because of the ++ // need to materialize the subgraph. ++ // So - do a local search by the Tarjan's ++ // algorithm and pretend that vertices ++ // with an index smaller than startIndex ++ // do not exist. ++ for (V v : graph.nodes()) { ++ int vI = toI(v); ++ if (vI < startIndex) { ++ continue; ++ } ++ if (!vIndex.containsKey(v)) { ++ getSCCs(startIndex, vI); ++ } ++ } ++ List<Set<V>> result = foundSCCs; ++ foundSCCs = null; ++ return result; ++ } ++ ++ private void getSCCs(int startIndex, int vertexIndex) ++ { ++ V vertex = toV(vertexIndex); ++ vIndex.put(vertex, index); ++ vLowlink.put(vertex, index); ++ index++; ++ path.push(vertex); ++ pathSet.add(vertex); ++ ++ Set<V> edges = graph.successors(vertex); ++ for (V successor : edges) { ++ int successorIndex = toI(successor); ++ if (successorIndex < startIndex) { ++ continue; ++ } ++ if (!vIndex.containsKey(successor)) { ++ getSCCs(startIndex, successorIndex); ++ vLowlink.put(vertex, Math.min(vLowlink.get(vertex), vLowlink.get(successor))); ++ } else if (pathSet.contains(successor)) { ++ vLowlink.put(vertex, Math.min(vLowlink.get(vertex), vIndex.get(successor))); ++ } ++ } ++ if (vLowlink.get(vertex).equals(vIndex.get(vertex))) { ++ Set<V> result = new HashSet<>(); ++ V temp; ++ do { ++ temp = path.pop(); ++ pathSet.remove(temp); ++ result.add(temp); ++ } while (!vertex.equals(temp)); ++ if (result.size() == 1) { ++ V v = result.iterator().next(); ++ if (graph.edges().contains(vertex)) { ++ foundSCCs.add(result); ++ } ++ } else { ++ foundSCCs.add(result); ++ } ++ } ++ } ++ ++ private boolean findCyclesInSCG(int startIndex, int vertexIndex, Graph<V> scg) ++ { ++ /* ++ * Find cycles in a strongly connected graph per Johnson. ++ */ ++ boolean foundCycle = false; ++ V vertex = toV(vertexIndex); ++ stack.push(vertex); ++ blocked.add(vertex); ++ ++ for (V successor : scg.successors(vertex)) { ++ int successorIndex = toI(successor); ++ if (successorIndex == startIndex) { ++ List<V> cycle = new ArrayList<>(stack.size()); ++ stack.descendingIterator().forEachRemaining(cycle::add); ++ cycleConsumer.accept(cycle); ++ foundCycle = true; ++ } else if (!blocked.contains(successor)) { ++ boolean gotCycle = findCyclesInSCG(startIndex, successorIndex, scg); ++ foundCycle = foundCycle || gotCycle; ++ } ++ } ++ if (foundCycle) { ++ unblock(vertex); ++ } else { ++ for (V w : scg.successors(vertex)) { ++ Set<V> bSet = getBSet(w); ++ bSet.add(vertex); ++ } ++ } ++ stack.pop(); ++ return foundCycle; ++ } ++ ++ private void unblock(V vertex) ++ { ++ blocked.remove(vertex); ++ Set<V> bSet = getBSet(vertex); ++ while (bSet.size() > 0) { ++ V w = bSet.iterator().next(); ++ bSet.remove(w); ++ if (blocked.contains(w)) { ++ unblock(w); ++ } ++ } ++ } ++ ++ @SuppressWarnings("unchecked") ++ private void initState(Consumer<List<V>> consumer) ++ { ++ cycleConsumer = consumer; ++ iToV = (V[]) graph.nodes().toArray(); ++ vToI = new HashMap<>(); ++ blocked = new HashSet<>(); ++ bSets = new HashMap<>(); ++ stack = new ArrayDeque<>(); ++ ++ for (int i = 0; i < iToV.length; i++) { ++ vToI.put(iToV[i], i); ++ } ++ } ++ ++ private void clearState() ++ { ++ cycleConsumer = null; ++ iToV = null; ++ vToI = null; ++ blocked = null; ++ bSets = null; ++ stack = null; ++ } ++ ++ private void initMinSCGState() ++ { ++ index = 0; ++ foundSCCs = new ArrayList<>(); ++ vIndex = new HashMap<>(); ++ vLowlink = new HashMap<>(); ++ path = new ArrayDeque<>(); ++ pathSet = new HashSet<>(); ++ } ++ ++ private void clearMinSCCState() ++ { ++ index = 0; ++ foundSCCs = null; ++ vIndex = null; ++ vLowlink = null; ++ path = null; ++ pathSet = null; ++ } ++ ++ private Integer toI(V vertex) ++ { ++ return vToI.get(vertex); ++ } ++ ++ private V toV(Integer i) ++ { ++ return iToV[i]; ++ } ++ ++ private Set<V> getBSet(V v) ++ { ++ // B sets typically not all needed, ++ // so instantiate lazily. ++ return bSets.computeIfAbsent(v, k -> new HashSet<>()); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.strategy; ++ ++import com.google.common.graph.GraphBuilder; ++import com.google.common.graph.MutableGraph; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import org.bukkit.plugin.UnknownDependencyException; ++ ++import java.util.ArrayList; ++import java.util.Collection; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Iterator; ++import java.util.LinkedList; ++import java.util.List; ++import java.util.Map; ++import java.util.Set; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++@SuppressWarnings("UnstableApiUsage") ++public class LegacyPluginLoadingStrategy<T> implements ProviderLoadingStrategy<T> { ++ ++ private static final Logger LOGGER = Logger.getLogger("LegacyPluginLoadingStrategy"); ++ private final ProviderConfiguration<T> configuration; ++ ++ public LegacyPluginLoadingStrategy(ProviderConfiguration<T> onLoad) { ++ this.configuration = onLoad; ++ } ++ ++ @Override ++ public List<T> loadProviders(List<PluginProvider<T>> providers) { ++ List<T> javapluginsLoaded = new ArrayList<>(); ++ MutableGraph<String> dependencyGraph = GraphBuilder.directed().build(); ++ GraphDependencyContext dependencyContext = new GraphDependencyContext(dependencyGraph); ++ ++ Map<String, PluginProvider<T>> providersToLoad = new HashMap<>(); ++ Set<String> loadedPlugins = new HashSet<>(); ++ Map<String, String> pluginsProvided = new HashMap<>(); ++ Map<String, Collection<String>> dependencies = new HashMap<>(); ++ Map<String, Collection<String>> softDependencies = new HashMap<>(); ++ ++ for (PluginProvider<T> provider : providers) { ++ PluginMeta configuration = provider.getMeta(); ++ ++ PluginProvider<T> replacedProvider = providersToLoad.put(configuration.getName(), provider); ++ if (replacedProvider != null) { ++ LOGGER.severe(String.format( ++ "Ambiguous plugin name `%s' for files `%s' and `%s' in `%s'", ++ configuration.getName(), ++ provider.getSource(), ++ replacedProvider.getSource(), ++ replacedProvider.getParentSource() ++ )); ++ } ++ ++ String removedProvided = pluginsProvided.remove(configuration.getName()); ++ if (removedProvided != null) { ++ LOGGER.warning(String.format( ++ "Ambiguous plugin name `%s'. It is also provided by `%s'", ++ configuration.getName(), ++ removedProvided ++ )); ++ } ++ ++ for (String provided : configuration.getProvidedPlugins()) { ++ PluginProvider<T> pluginProvider = providersToLoad.get(provided); ++ ++ if (pluginProvider != null) { ++ LOGGER.warning(String.format( ++ "`%s provides `%s' while this is also the name of `%s' in `%s'", ++ provider.getSource(), ++ provided, ++ pluginProvider.getSource(), ++ provider.getParentSource() ++ )); ++ } else { ++ String replacedPlugin = pluginsProvided.put(provided, configuration.getName()); ++ if (replacedPlugin != null) { ++ LOGGER.warning(String.format( ++ "`%s' is provided by both `%s' and `%s'", ++ provided, ++ configuration.getName(), ++ replacedPlugin ++ )); ++ } ++ } ++ } ++ ++ Collection<String> softDependencySet = this.configuration.optionalDependencies(provider); ++ if (softDependencySet != null && !softDependencySet.isEmpty()) { ++ if (softDependencies.containsKey(configuration.getName())) { ++ // Duplicates do not matter, they will be removed together if applicable ++ softDependencies.get(configuration.getName()).addAll(softDependencySet); ++ } else { ++ softDependencies.put(configuration.getName(), new LinkedList<String>(softDependencySet)); ++ } ++ ++ for (String depend : softDependencySet) { ++ dependencyGraph.putEdge(configuration.getName(), depend); ++ } ++ } ++ ++ Collection<String> dependencySet = this.configuration.requiredDependencies(provider); ++ if (dependencySet != null && !dependencySet.isEmpty()) { ++ dependencies.put(configuration.getName(), new LinkedList<String>(dependencySet)); ++ ++ for (String depend : dependencySet) { ++ dependencyGraph.putEdge(configuration.getName(), depend); ++ } ++ } ++ ++ Collection<String> loadBeforeSet = this.configuration.loadBeforeDependencies(provider); ++ if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) { ++ for (String loadBeforeTarget : loadBeforeSet) { ++ if (softDependencies.containsKey(loadBeforeTarget)) { ++ softDependencies.get(loadBeforeTarget).add(configuration.getName()); ++ } else { ++ // softDependencies is never iterated, so 'ghost' plugins aren't an issue ++ Collection<String> shortSoftDependency = new LinkedList<String>(); ++ shortSoftDependency.add(configuration.getName()); ++ softDependencies.put(loadBeforeTarget, shortSoftDependency); ++ } ++ ++ dependencyGraph.putEdge(loadBeforeTarget, configuration.getName()); ++ } ++ } ++ } ++ ++ while (!providersToLoad.isEmpty()) { ++ boolean missingDependency = true; ++ Iterator<Map.Entry<String, PluginProvider<T>>> providerIterator = providersToLoad.entrySet().iterator(); ++ ++ while (providerIterator.hasNext()) { ++ Map.Entry<String, PluginProvider<T>> entry = providerIterator.next(); ++ String providerIdentifier = entry.getKey(); ++ ++ if (dependencies.containsKey(providerIdentifier)) { ++ Iterator<String> dependencyIterator = dependencies.get(providerIdentifier).iterator(); ++ final Set<String> missingHardDependencies = new HashSet<>(dependencies.get(providerIdentifier).size()); // Paper - list all missing hard depends ++ ++ while (dependencyIterator.hasNext()) { ++ String dependency = dependencyIterator.next(); ++ ++ // Dependency loaded ++ if (loadedPlugins.contains(dependency)) { ++ dependencyIterator.remove(); ++ ++ // We have a dependency not found ++ } else if (!providersToLoad.containsKey(dependency) && !pluginsProvided.containsKey(dependency)) { ++ // Paper start ++ missingHardDependencies.add(dependency); ++ } ++ } ++ if (!missingHardDependencies.isEmpty()) { ++ // Paper end ++ missingDependency = false; ++ providerIterator.remove(); ++ pluginsProvided.values().removeIf(s -> s.equals(providerIdentifier)); // Paper - remove provided plugins ++ softDependencies.remove(providerIdentifier); ++ dependencies.remove(providerIdentifier); ++ ++ LOGGER.log( ++ Level.SEVERE, ++ "Could not load '" + entry.getValue().getSource() + "' in folder '" + entry.getValue().getParentSource() + "'", // Paper ++ new UnknownDependencyException(missingHardDependencies, providerIdentifier)); // Paper ++ } ++ ++ if (dependencies.containsKey(providerIdentifier) && dependencies.get(providerIdentifier).isEmpty()) { ++ dependencies.remove(providerIdentifier); ++ } ++ } ++ if (softDependencies.containsKey(providerIdentifier)) { ++ Iterator<String> softDependencyIterator = softDependencies.get(providerIdentifier).iterator(); ++ ++ while (softDependencyIterator.hasNext()) { ++ String softDependency = softDependencyIterator.next(); ++ ++ // Soft depend is no longer around ++ if (!providersToLoad.containsKey(softDependency) && !pluginsProvided.containsKey(softDependency)) { ++ softDependencyIterator.remove(); ++ } ++ } ++ ++ if (softDependencies.get(providerIdentifier).isEmpty()) { ++ softDependencies.remove(providerIdentifier); ++ } ++ } ++ if (!(dependencies.containsKey(providerIdentifier) || softDependencies.containsKey(providerIdentifier)) && providersToLoad.containsKey(providerIdentifier)) { ++ // We're clear to load, no more soft or hard dependencies left ++ PluginProvider<T> file = providersToLoad.get(providerIdentifier); ++ providerIterator.remove(); ++ pluginsProvided.values().removeIf(s -> s.equals(providerIdentifier)); // Paper - remove provided plugins ++ missingDependency = false; ++ ++ try { ++ this.configuration.applyContext(file, dependencyContext); ++ T loadedPlugin = file.createInstance(); ++ ++ if (this.configuration.load(file, loadedPlugin)) { ++ loadedPlugins.add(file.getMeta().getName()); ++ loadedPlugins.addAll(file.getMeta().getProvidedPlugins()); ++ javapluginsLoaded.add(loadedPlugin); ++ } ++ ++ } catch (Exception ex) { ++ LOGGER.log(Level.SEVERE, "Could not load '" + file.getSource() + "' in folder '" + file.getParentSource() + "'", ex); // Paper ++ } ++ } ++ } ++ ++ if (missingDependency) { ++ // We now iterate over plugins until something loads ++ // This loop will ignore soft dependencies ++ providerIterator = providersToLoad.entrySet().iterator(); ++ ++ while (providerIterator.hasNext()) { ++ Map.Entry<String, PluginProvider<T>> entry = providerIterator.next(); ++ String plugin = entry.getKey(); ++ ++ if (!dependencies.containsKey(plugin)) { ++ softDependencies.remove(plugin); ++ missingDependency = false; ++ PluginProvider<T> file = entry.getValue(); ++ providerIterator.remove(); ++ ++ try { ++ this.configuration.applyContext(file, dependencyContext); ++ T loadedPlugin = file.createInstance(); ++ ++ if (this.configuration.load(file, loadedPlugin)) { ++ loadedPlugins.add(file.getMeta().getName()); ++ loadedPlugins.addAll(file.getMeta().getProvidedPlugins()); ++ javapluginsLoaded.add(loadedPlugin); ++ } ++ break; ++ } catch (Exception ex) { ++ LOGGER.log(Level.SEVERE, "Could not load '" + file.getSource() + "' in folder '" + file.getParentSource() + "'", ex); // Paper ++ } ++ } ++ } ++ // We have no plugins left without a depend ++ if (missingDependency) { ++ softDependencies.clear(); ++ dependencies.clear(); ++ Iterator<PluginProvider<T>> failedPluginIterator = providersToLoad.values().iterator(); ++ ++ while (failedPluginIterator.hasNext()) { ++ PluginProvider<T> file = failedPluginIterator.next(); ++ failedPluginIterator.remove(); ++ LOGGER.log(Level.SEVERE, "Could not load '" + file.getSource() + "' in folder '" + file.getParentSource() + "': circular dependency detected"); // Paper ++ } ++ } ++ } ++ } ++ ++ return javapluginsLoaded; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.strategy; ++ ++import com.google.common.collect.Lists; ++import com.google.common.graph.GraphBuilder; ++import com.google.common.graph.MutableGraph; ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import org.bukkit.plugin.UnknownDependencyException; ++import org.slf4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++@SuppressWarnings("UnstableApiUsage") ++public class ModernPluginLoadingStrategy<T> implements ProviderLoadingStrategy<T> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ private final ProviderConfiguration<T> configuration; ++ ++ public ModernPluginLoadingStrategy(ProviderConfiguration<T> onLoad) { ++ this.configuration = onLoad; ++ } ++ ++ @Override ++ public List<T> loadProviders(List<PluginProvider<T>> pluginProviders) { ++ MutableGraph<String> dependencyGraph = GraphBuilder.directed().build(); ++ Map<String, PluginProviderEntry<T>> providerMap = new HashMap<>(); ++ List<PluginProvider<T>> validatedProviders = new ArrayList<>(); ++ ++ // Populate provider map ++ for (PluginProvider<T> provider : pluginProviders) { ++ PluginMeta providerConfig = provider.getMeta(); ++ PluginProviderEntry<T> entry = new PluginProviderEntry<>(provider); ++ ++ PluginProviderEntry<T> replacedProvider = providerMap.put(providerConfig.getName(), entry); ++ if (replacedProvider != null) { ++ LOGGER.error(String.format( ++ "Ambiguous plugin name '%s' for files '%s' and '%s' in '%s'", ++ providerConfig.getName(), ++ provider.getSource(), ++ replacedProvider.provider.getSource(), ++ replacedProvider.provider.getParentSource() ++ )); ++ } ++ ++ for (String extra : providerConfig.getProvidedPlugins()) { ++ PluginProviderEntry<T> replacedExtraProvider = providerMap.putIfAbsent(extra, entry); ++ if (replacedExtraProvider != null) { ++ LOGGER.warn(String.format( ++ "`%s' is provided by both `%s' and `%s'", ++ extra, ++ providerConfig.getName(), ++ replacedExtraProvider.provider.getMeta().getName() ++ )); ++ } ++ } ++ } ++ ++ // Validate providers, ensuring all of them have valid dependencies. Removing those who are invalid ++ for (PluginProvider<T> provider : pluginProviders) { ++ PluginMeta configuration = provider.getMeta(); ++ ++ // Populate missing dependencies to capture if there are multiple missing ones. ++ List<String> missingDependencies = new ArrayList<>(); ++ for (String hardDependency : this.configuration.requiredDependencies(provider)) { ++ if (!providerMap.containsKey(hardDependency)) { ++ missingDependencies.add(hardDependency); ++ } ++ } ++ ++ if (missingDependencies.isEmpty()) { ++ validatedProviders.add(provider); ++ } else { ++ LOGGER.error("Could not load '%s' in '%s'".formatted(provider.getSource(), provider.getParentSource()), new UnknownDependencyException(missingDependencies, configuration.getName())); // Paper ++ // Because the validator is invalid, remove it from the provider map ++ providerMap.remove(configuration.getName()); ++ } ++ } ++ ++ for (PluginProvider<?> validated : validatedProviders) { ++ PluginMeta configuration = validated.getMeta(); ++ ++ // Build a validated provider's dependencies into the graph ++ DependencyUtil.buildDependencyGraph(dependencyGraph, configuration); ++ ++ // Add the provided plugins to the graph as well ++ for (String provides : configuration.getProvidedPlugins()) { ++ DependencyUtil.addProvidedPlugin(dependencyGraph, configuration.getName(), provides); ++ } ++ } ++ ++ // Reverse the topographic search to let us see which providers we can load first. ++ List<String> reversedTopographicSort; ++ try { ++ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(dependencyGraph)); ++ } catch (TopographicGraphSorter.GraphCycleException exception) { ++ throw new PluginGraphCycleException(new JohnsonSimpleCycles<>(dependencyGraph).findSimpleCycles()); ++ } ++ ++ GraphDependencyContext graphDependencyContext = new GraphDependencyContext(dependencyGraph); ++ List<T> loadedPlugins = new ArrayList<>(); ++ for (String providerIdentifier : reversedTopographicSort) { ++ // It's possible that this will be null because the above dependencies for soft/load before aren't validated if they exist. ++ // The graph could be MutableGraph<PluginProvider<T>>, but we would have to check if each dependency exists there... just ++ // nicer to do it here TBH. ++ PluginProviderEntry<T> retrievedProviderEntry = providerMap.get(providerIdentifier); ++ if (retrievedProviderEntry == null || retrievedProviderEntry.provided) { ++ // OR if this was already provided (most likely from a plugin that already "provides" that dependency) ++ // This won't matter since the provided plugin is loaded as a dependency, meaning it should have been loaded correctly anyways ++ continue; // Skip provider that doesn't exist.... ++ } ++ retrievedProviderEntry.provided = true; ++ PluginProvider<T> retrievedProvider = retrievedProviderEntry.provider; ++ try { ++ this.configuration.applyContext(retrievedProvider, graphDependencyContext); ++ ++ T instance = retrievedProvider.createInstance(); ++ if (this.configuration.load(retrievedProvider, instance)) { ++ loadedPlugins.add(instance); ++ } ++ } catch (Exception ex) { ++ LOGGER.error("Could not load plugin '%s' in folder '%s'".formatted(retrievedProvider.getFileName(), retrievedProvider.getParentSource()), ex); // Paper ++ } ++ } ++ ++ return loadedPlugins; ++ } ++ ++ private static class PluginProviderEntry<T> { ++ ++ private final PluginProvider<T> provider; ++ private boolean provided; ++ ++ private PluginProviderEntry(PluginProvider<T> provider) { ++ this.provider = provider; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.strategy; ++ ++import java.util.List; ++ ++/** ++ * Indicates a dependency cycle within a provider loading sequence. ++ */ ++public class PluginGraphCycleException extends RuntimeException { ++ ++ private final List<List<String>> cycles; ++ ++ public PluginGraphCycleException(List<List<String>> cycles) { ++ this.cycles = cycles; ++ } ++ ++ public List<List<String>> getCycles() { ++ return this.cycles; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.strategy; ++ ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++ ++import java.util.List; ++ ++/** ++ * Used to share code with the modern and legacy plugin load strategy. ++ * ++ * @param <T> ++ */ ++public interface ProviderConfiguration<T> { ++ ++ void applyContext(PluginProvider<T> provider, DependencyContext dependencyContext); ++ ++ boolean load(PluginProvider<T> provider, T provided); ++ ++ List<String> requiredDependencies(PluginProvider<T> provider); ++ ++ List<String> optionalDependencies(PluginProvider<T> provider); ++ ++ List<String> loadBeforeDependencies(PluginProvider<T> provider); ++ ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.strategy; ++ ++import io.papermc.paper.plugin.provider.PluginProvider; ++ ++import java.util.List; ++ ++/** ++ * Used by a {@link io.papermc.paper.plugin.storage.SimpleProviderStorage} to load plugin providers in a certain order. ++ * <p> ++ * Returns providers loaded. ++ * @param <P> provider type ++ */ ++public interface ProviderLoadingStrategy<P> { ++ ++ List<P> loadProviders(List<PluginProvider<P>> providers); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/TopographicGraphSorter.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/TopographicGraphSorter.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/TopographicGraphSorter.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.entrypoint.strategy; ++ ++import com.google.common.graph.Graph; ++ ++import java.util.ArrayDeque; ++import java.util.ArrayList; ++import java.util.Deque; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++ ++public class TopographicGraphSorter { ++ ++ // Topographically sort dependencies ++ public static <N> List<N> sortGraph(Graph<N> graph) throws PluginGraphCycleException { ++ List<N> sorted = new ArrayList<>(); ++ Deque<N> roots = new ArrayDeque<>(); ++ Map<N, Integer> nonRoots = new HashMap<>(); ++ ++ for (N node : graph.nodes()) { ++ // Is a node being referred to by any other nodes? ++ int degree = graph.inDegree(node); ++ if (degree == 0) { ++ // Is a root ++ roots.add(node); ++ } else { ++ // Isn't a root, the number represents how many nodes connect to it. ++ nonRoots.put(node, degree); ++ } ++ } ++ ++ // Pick from nodes that aren't referred to anywhere else ++ while (!roots.isEmpty()) { ++ N next = roots.remove(); ++ ++ for (N successor : graph.successors(next)) { ++ // Traverse through, moving down a degree ++ int newInDegree = nonRoots.get(successor) - 1; ++ ++ if (newInDegree == 0) { ++ nonRoots.remove(successor); ++ roots.add(successor); ++ } else { ++ nonRoots.put(successor, newInDegree); ++ } ++ ++ } ++ sorted.add(next); ++ } ++ ++ if (!nonRoots.isEmpty()) { ++ throw new GraphCycleException(); ++ } ++ ++ return sorted; ++ } ++ ++ public static class GraphCycleException extends RuntimeException { ++ ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader; ++ ++import io.papermc.paper.plugin.bootstrap.PluginProviderContext; ++import io.papermc.paper.plugin.loader.library.ClassPathLibrary; ++import io.papermc.paper.plugin.loader.library.PaperLibraryStore; ++import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import org.jetbrains.annotations.NotNull; ++ ++import java.io.IOException; ++import java.net.MalformedURLException; ++import java.net.URL; ++import java.net.URLClassLoader; ++import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.jar.JarFile; ++import java.util.logging.Logger; ++ ++public class PaperClasspathBuilder implements PluginClasspathBuilder { ++ ++ private final List<ClassPathLibrary> libraries = new ArrayList<>(); ++ ++ private final PluginProviderContext context; ++ ++ public PaperClasspathBuilder(PluginProviderContext context) { ++ this.context = context; ++ } ++ ++ @Override ++ public @NotNull PluginProviderContext getContext() { ++ return this.context; ++ } ++ ++ @Override ++ public @NotNull PluginClasspathBuilder addLibrary(@NotNull ClassPathLibrary classPathLibrary) { ++ this.libraries.add(classPathLibrary); ++ return this; ++ } ++ ++ public PaperPluginClassLoader buildClassLoader(Logger logger, Path source, JarFile jarFile, PaperPluginMeta configuration) { ++ PaperLibraryStore paperLibraryStore = new PaperLibraryStore(); ++ for (ClassPathLibrary library : this.libraries) { ++ library.register(paperLibraryStore); ++ } ++ ++ List<Path> paths = paperLibraryStore.getPaths(); ++ URL[] urls = new URL[paths.size()]; ++ for (int i = 0; i < paths.size(); i++) { ++ Path path = paperLibraryStore.getPaths().get(i); ++ try { ++ urls[i] = path.toUri().toURL(); ++ } catch (MalformedURLException e) { ++ throw new AssertionError(e); ++ } ++ } ++ ++ try { ++ return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls)); ++ } catch (IOException exception) { ++ throw new RuntimeException(exception); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/PaperLibraryStore.java b/src/main/java/io/papermc/paper/plugin/loader/library/PaperLibraryStore.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/loader/library/PaperLibraryStore.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.loader.library; ++ ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.List; ++ ++public class PaperLibraryStore implements LibraryStore { ++ ++ private final List<Path> paths = new ArrayList<>(); ++ ++ @Override ++ public void addLibrary(@NotNull Path library) { ++ this.paths.add(library); ++ } ++ ++ public List<Path> getPaths() { ++ return this.paths; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; ++import io.papermc.paper.plugin.storage.ServerPluginProviderStorage; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.slf4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class MultiRuntimePluginProviderStorage extends ServerPluginProviderStorage { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ private final List<JavaPlugin> provided = new ArrayList<>(); ++ ++ @Override ++ public void register(PluginProvider<JavaPlugin> provider) { ++ if (provider instanceof PaperPluginParent.PaperServerPluginProvider) { ++ LOGGER.warn("Skipping loading of paper plugin requested from SimplePluginManager."); ++ return; ++ } ++ super.register(provider); ++ /* ++ Register the provider into the server entrypoint, this allows it to show in /plugins correctly. Generally it might be better in the future to make a separate storage, ++ as putting it into the entrypoint handlers doesn't make much sense. ++ */ ++ LaunchEntryPointHandler.INSTANCE.register(Entrypoint.PLUGIN, provider); ++ } ++ ++ @Override ++ public void processProvided(JavaPlugin provided) { ++ super.processProvided(provided); ++ this.provided.add(provided); ++ } ++ ++ @Override ++ public boolean exitOnCycleDependencies() { ++ return false; ++ } ++ ++ public List<JavaPlugin> getLoaded() { ++ return this.provided; ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java b/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import org.bukkit.permissions.Permissible; ++import org.bukkit.permissions.Permission; ++ ++import java.util.HashMap; ++import java.util.LinkedHashMap; ++import java.util.LinkedHashSet; ++import java.util.Map; ++import java.util.Set; ++ ++class NormalPaperPermissionManager extends PaperPermissionManager { ++ ++ private final Map<String, Permission> permissions = new HashMap<>(); ++ private final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<>(); ++ private final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<>(); ++ private final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<>(); ++ ++ public NormalPaperPermissionManager() { ++ this.defaultPerms().put(true, new LinkedHashSet<>()); ++ this.defaultPerms().put(false, new LinkedHashSet<>()); ++ } ++ ++ @Override ++ public Map<String, Permission> permissions() { ++ return this.permissions; ++ } ++ ++ @Override ++ public Map<Boolean, Set<Permission>> defaultPerms() { ++ return this.defaultPerms; ++ } ++ ++ @Override ++ public Map<String, Map<Permissible, Boolean>> permSubs() { ++ return this.permSubs; ++ } ++ ++ @Override ++ public Map<Boolean, Map<Permissible, Boolean>> defSubs() { ++ return this.defSubs; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import co.aikar.timings.TimedEventExecutor; ++import com.destroystokyo.paper.event.server.ServerExceptionEvent; ++import com.destroystokyo.paper.exception.ServerEventException; ++import com.google.common.collect.Sets; ++import org.bukkit.Server; ++import org.bukkit.Warning; ++import org.bukkit.event.Event; ++import org.bukkit.event.EventHandler; ++import org.bukkit.event.EventPriority; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.Listener; ++import org.bukkit.plugin.AuthorNagException; ++import org.bukkit.plugin.EventExecutor; ++import org.bukkit.plugin.IllegalPluginAccessException; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.RegisteredListener; ++import org.jetbrains.annotations.NotNull; ++ ++import java.lang.reflect.Method; ++import java.util.Arrays; ++import java.util.HashMap; ++import java.util.HashSet; ++import java.util.Map; ++import java.util.Set; ++import java.util.logging.Level; ++ ++class PaperEventManager { ++ ++ private final Server server; ++ ++ public PaperEventManager(Server server) { ++ this.server = server; ++ } ++ ++ // SimplePluginManager ++ public void callEvent(@NotNull Event event) { ++ if (event.isAsynchronous() && this.server.isPrimaryThread()) { ++ throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously."); ++ } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) { ++ throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously."); ++ } ++ ++ HandlerList handlers = event.getHandlers(); ++ RegisteredListener[] listeners = handlers.getRegisteredListeners(); ++ ++ for (RegisteredListener registration : listeners) { ++ if (!registration.getPlugin().isEnabled()) { ++ continue; ++ } ++ ++ try { ++ registration.callEvent(event); ++ } catch (AuthorNagException ex) { ++ Plugin plugin = registration.getPlugin(); ++ ++ if (plugin.isNaggable()) { ++ plugin.setNaggable(false); ++ ++ this.server.getLogger().log(Level.SEVERE, String.format( ++ "Nag author(s): '%s' of '%s' about the following: %s", ++ plugin.getPluginMeta().getAuthors(), ++ plugin.getPluginMeta().getDisplayName(), ++ ex.getMessage() ++ )); ++ } ++ } catch (Throwable ex) { ++ String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getPluginMeta().getDisplayName(); ++ this.server.getLogger().log(Level.SEVERE, msg, ex); ++ if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop ++ this.callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event))); ++ } ++ } ++ } ++ } ++ ++ public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) { ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled"); ++ } ++ ++ for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : this.createRegisteredListeners(listener, plugin).entrySet()) { ++ this.getEventListeners(this.getRegistrationClass(entry.getKey())).registerAll(entry.getValue()); ++ } ++ ++ } ++ ++ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin) { ++ this.registerEvent(event, listener, priority, executor, plugin, false); ++ } ++ ++ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin, boolean ignoreCancelled) { ++ if (!plugin.isEnabled()) { ++ throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled"); ++ } ++ ++ executor = new TimedEventExecutor(executor, plugin, null, event); ++ this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled)); ++ } ++ ++ @NotNull ++ private HandlerList getEventListeners(@NotNull Class<? extends Event> type) { ++ try { ++ Method method = this.getRegistrationClass(type).getDeclaredMethod("getHandlerList"); ++ method.setAccessible(true); ++ return (HandlerList) method.invoke(null); ++ } catch (Exception e) { ++ throw new IllegalPluginAccessException(e.toString()); ++ } ++ } ++ ++ @NotNull ++ private Class<? extends Event> getRegistrationClass(@NotNull Class<? extends Event> clazz) { ++ try { ++ clazz.getDeclaredMethod("getHandlerList"); ++ return clazz; ++ } catch (NoSuchMethodException e) { ++ if (clazz.getSuperclass() != null ++ && !clazz.getSuperclass().equals(Event.class) ++ && Event.class.isAssignableFrom(clazz.getSuperclass())) { ++ return this.getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class)); ++ } else { ++ throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!"); ++ } ++ } ++ } ++ ++ // JavaPluginLoader ++ @NotNull ++ public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(@NotNull Listener listener, @NotNull final Plugin plugin) { ++ Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<>(); ++ ++ Set<Method> methods; ++ try { ++ Class<?> listenerClazz = listener.getClass(); ++ methods = Sets.union( ++ Set.of(listenerClazz.getMethods()), ++ Set.of(listenerClazz.getDeclaredMethods()) ++ ); ++ } catch (NoClassDefFoundError e) { ++ plugin.getLogger().severe("Failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist."); ++ return ret; ++ } ++ ++ for (final Method method : methods) { ++ final EventHandler eh = method.getAnnotation(EventHandler.class); ++ if (eh == null) continue; ++ // Do not register bridge or synthetic methods to avoid event duplication ++ // Fixes SPIGOT-893 ++ if (method.isBridge() || method.isSynthetic()) { ++ continue; ++ } ++ final Class<?> checkClass; ++ if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) { ++ plugin.getLogger().severe(plugin.getPluginMeta().getDisplayName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass()); ++ continue; ++ } ++ final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class); ++ method.setAccessible(true); ++ Set<RegisteredListener> eventSet = ret.computeIfAbsent(eventClass, k -> new HashSet<>()); ++ ++ for (Class<?> clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) { ++ // This loop checks for extending deprecated events ++ if (clazz.getAnnotation(Deprecated.class) != null) { ++ Warning warning = clazz.getAnnotation(Warning.class); ++ Warning.WarningState warningState = this.server.getWarningState(); ++ if (!warningState.printFor(warning)) { ++ break; ++ } ++ plugin.getLogger().log( ++ Level.WARNING, ++ String.format( ++ "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated. \"%s\"; please notify the authors %s.", ++ plugin.getPluginMeta().getDisplayName(), ++ clazz.getName(), ++ method.toGenericString(), ++ (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected", ++ Arrays.toString(plugin.getPluginMeta().getAuthors().toArray())), ++ warningState == Warning.WarningState.ON ? new AuthorNagException(null) : null); ++ break; ++ } ++ } ++ ++ EventExecutor executor = new TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass); ++ eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled())); ++ } ++ return ret; ++ } ++ ++ public void clearEvents() { ++ HandlerList.unregisterAll(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import com.google.common.collect.ImmutableSet; ++import io.papermc.paper.plugin.PermissionManager; ++import org.bukkit.permissions.Permissible; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.HashSet; ++import java.util.List; ++import java.util.Locale; ++import java.util.Map; ++import java.util.Set; ++import java.util.WeakHashMap; ++ ++/** ++ * See ++ * {@link StupidSPMPermissionManagerWrapper} ++ */ ++abstract class PaperPermissionManager implements PermissionManager { ++ ++ public abstract Map<String, Permission> permissions(); ++ ++ public abstract Map<Boolean, Set<Permission>> defaultPerms(); ++ ++ public abstract Map<String, Map<Permissible, Boolean>> permSubs(); ++ ++ public abstract Map<Boolean, Map<Permissible, Boolean>> defSubs(); ++ ++ @Override ++ @Nullable ++ public Permission getPermission(@NotNull String name) { ++ return this.permissions().get(name.toLowerCase(java.util.Locale.ENGLISH)); ++ } ++ ++ @Override ++ public void addPermission(@NotNull Permission perm) { ++ this.addPermission(perm, true); ++ } ++ ++ @Override ++ public void addPermissions(@NotNull List<Permission> permissions) { ++ for (Permission permission : permissions) { ++ this.addPermission(permission, false); ++ } ++ this.dirtyPermissibles(); ++ } ++ ++ // Allow suppressing permission default calculations ++ private void addPermission(@NotNull Permission perm, boolean dirty) { ++ String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH); ++ ++ if (this.permissions().containsKey(name)) { ++ throw new IllegalArgumentException("The permission " + name + " is already defined!"); ++ } ++ ++ this.permissions().put(name, perm); ++ this.calculatePermissionDefault(perm, dirty); ++ } ++ ++ @Override ++ @NotNull ++ public Set<Permission> getDefaultPermissions(boolean op) { ++ return ImmutableSet.copyOf(this.defaultPerms().get(op)); ++ } ++ ++ ++ @Override ++ public void removePermission(@NotNull Permission perm) { ++ this.removePermission(perm.getName()); ++ } ++ ++ ++ @Override ++ public void removePermission(@NotNull String name) { ++ this.permissions().remove(name.toLowerCase(java.util.Locale.ENGLISH)); ++ } ++ ++ @Override ++ public void recalculatePermissionDefaults(@NotNull Permission perm) { ++ // we need a null check here because some plugins for some unknown reason pass null into this? ++ if (perm != null && this.permissions().containsKey(perm.getName().toLowerCase(Locale.ENGLISH))) { ++ this.defaultPerms().get(true).remove(perm); ++ this.defaultPerms().get(false).remove(perm); ++ ++ this.calculatePermissionDefault(perm, true); ++ } ++ } ++ ++ private void calculatePermissionDefault(@NotNull Permission perm, boolean dirty) { ++ if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) { ++ this.defaultPerms().get(true).add(perm); ++ if (dirty) { ++ this.dirtyPermissibles(true); ++ } ++ } ++ if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) { ++ this.defaultPerms().get(false).add(perm); ++ if (dirty) { ++ this.dirtyPermissibles(false); ++ } ++ } ++ } ++ ++ ++ @Override ++ public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) { ++ String name = permission.toLowerCase(java.util.Locale.ENGLISH); ++ Map<Permissible, Boolean> map = this.permSubs().computeIfAbsent(name, k -> new WeakHashMap<>()); ++ ++ map.put(permissible, true); ++ } ++ ++ @Override ++ public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) { ++ String name = permission.toLowerCase(java.util.Locale.ENGLISH); ++ Map<Permissible, Boolean> map = this.permSubs().get(name); ++ ++ if (map != null) { ++ map.remove(permissible); ++ ++ if (map.isEmpty()) { ++ this.permSubs().remove(name); ++ } ++ } ++ } ++ ++ @Override ++ @NotNull ++ public Set<Permissible> getPermissionSubscriptions(@NotNull String permission) { ++ String name = permission.toLowerCase(java.util.Locale.ENGLISH); ++ Map<Permissible, Boolean> map = this.permSubs().get(name); ++ ++ if (map == null) { ++ return ImmutableSet.of(); ++ } else { ++ return ImmutableSet.copyOf(map.keySet()); ++ } ++ } ++ ++ @Override ++ public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ Map<Permissible, Boolean> map = this.defSubs().computeIfAbsent(op, k -> new WeakHashMap<>()); ++ ++ map.put(permissible, true); ++ } ++ ++ @Override ++ public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ Map<Permissible, Boolean> map = this.defSubs().get(op); ++ ++ if (map != null) { ++ map.remove(permissible); ++ ++ if (map.isEmpty()) { ++ this.defSubs().remove(op); ++ } ++ } ++ } ++ ++ @Override ++ @NotNull ++ public Set<Permissible> getDefaultPermSubscriptions(boolean op) { ++ Map<Permissible, Boolean> map = this.defSubs().get(op); ++ ++ if (map == null) { ++ return ImmutableSet.of(); ++ } else { ++ return ImmutableSet.copyOf(map.keySet()); ++ } ++ } ++ ++ @Override ++ @NotNull ++ public Set<Permission> getPermissions() { ++ return new HashSet<>(this.permissions().values()); ++ } ++ ++ @Override ++ public void clearPermissions() { ++ this.permissions().clear(); ++ this.defaultPerms().get(true).clear(); ++ this.defaultPerms().get(false).clear(); ++ } ++ ++ ++ void dirtyPermissibles(boolean op) { ++ Set<Permissible> permissibles = this.getDefaultPermSubscriptions(op); ++ ++ for (Permissible p : permissibles) { ++ p.recalculatePermissions(); ++ } ++ } ++ ++ void dirtyPermissibles() { ++ this.dirtyPermissibles(true); ++ this.dirtyPermissibles(false); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import com.google.common.base.Preconditions; ++import com.google.common.graph.GraphBuilder; ++import com.google.common.graph.MutableGraph; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil; ++import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; ++import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; ++import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader; ++import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.provider.source.DirectoryProviderSource; ++import io.papermc.paper.plugin.provider.source.FileProviderSource; ++import org.bukkit.Bukkit; ++import org.bukkit.Server; ++import org.bukkit.World; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandMap; ++import org.bukkit.command.PluginCommandYamlParser; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.server.PluginDisableEvent; ++import org.bukkit.event.server.PluginEnableEvent; ++import org.bukkit.plugin.InvalidDescriptionException; ++import org.bukkit.plugin.InvalidPluginException; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.PluginManager; ++import org.bukkit.plugin.UnknownDependencyException; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.spongepowered.configurate.serialize.SerializationException; ++ ++import java.io.IOException; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.logging.Level; ++ ++@SuppressWarnings("UnstableApiUsage") ++class PaperPluginInstanceManager { ++ ++ private static final FileProviderSource FILE_PROVIDER_SOURCE = new FileProviderSource("File '%s'"::formatted); ++ private static final DirectoryProviderSource DIRECTORY_PROVIDER_SOURCE = new DirectoryProviderSource(); ++ ++ private final List<Plugin> plugins = new ArrayList<>(); ++ private final Map<String, Plugin> lookupNames = new HashMap<>(); ++ ++ private final PluginManager pluginManager; ++ private final CommandMap commandMap; ++ private final Server server; ++ ++ private final MutableGraph<String> dependencyGraph = GraphBuilder.directed().build(); ++ private final DependencyContext context = new GraphDependencyContext(this.dependencyGraph); ++ ++ public PaperPluginInstanceManager(PluginManager pluginManager, CommandMap commandMap, Server server) { ++ this.commandMap = commandMap; ++ this.server = server; ++ this.pluginManager = pluginManager; ++ } ++ ++ public @Nullable Plugin getPlugin(@NotNull String name) { ++ return this.lookupNames.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); // Paper ++ } ++ ++ public @NotNull Plugin[] getPlugins() { ++ return this.plugins.toArray(new Plugin[0]); ++ } ++ ++ public boolean isPluginEnabled(@NotNull String name) { ++ Plugin plugin = this.getPlugin(name); ++ ++ return this.isPluginEnabled(plugin); ++ } ++ ++ public synchronized boolean isPluginEnabled(@Nullable Plugin plugin) { ++ if ((plugin != null) && (this.plugins.contains(plugin))) { ++ return plugin.isEnabled(); ++ } else { ++ return false; ++ } ++ } ++ ++ public void loadPlugin(Plugin provided) { ++ PluginMeta configuration = provided.getPluginMeta(); ++ ++ this.plugins.add(provided); ++ this.lookupNames.put(configuration.getName().toLowerCase(java.util.Locale.ENGLISH), provided); ++ for (String providedPlugin : configuration.getProvidedPlugins()) { ++ this.lookupNames.putIfAbsent(providedPlugin.toLowerCase(java.util.Locale.ENGLISH), provided); ++ } ++ ++ DependencyUtil.buildDependencyGraph(this.dependencyGraph, configuration); ++ } ++ ++ // InvalidDescriptionException is never used, because the old JavaPluginLoader would wrap the exception. ++ public @Nullable Plugin loadPlugin(@NotNull Path path) throws InvalidPluginException, UnknownDependencyException { ++ RuntimePluginEntrypointHandler<SingularRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new SingularRuntimePluginProviderStorage()); ++ ++ try { ++ FILE_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, path); ++ } catch (IllegalArgumentException exception) { ++ return null; // Return null when the plugin file is not valid / plugin type is unknown ++ } catch (PluginGraphCycleException exception) { ++ throw new InvalidPluginException("Cannot import plugin that causes cyclic dependencies!"); ++ } catch (SerializationException | ++ InvalidDescriptionException ex) { // The spigot implementation wraps it in an invalid plugin exception ++ throw new InvalidPluginException(ex); ++ } catch (Exception e) { ++ throw new InvalidPluginException(e); ++ } ++ ++ try { ++ runtimePluginEntrypointHandler.enter(Entrypoint.PLUGIN); ++ } catch (Throwable e) { ++ throw new InvalidPluginException(e); ++ } ++ ++ return runtimePluginEntrypointHandler.getPluginProviderStorage().getSingleLoaded() ++ .orElseThrow(() -> new InvalidPluginException("Plugin didn't load any plugin providers?")); ++ } ++ ++ // The behavior of this is that all errors are logged instead of being thrown ++ public @NotNull Plugin[] loadPlugins(@NotNull Path directory) { ++ Preconditions.checkArgument(Files.isDirectory(directory), "Directory must be a directory"); // Avoid creating a directory if it doesn't exist ++ ++ RuntimePluginEntrypointHandler<MultiRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new MultiRuntimePluginProviderStorage()); ++ try { ++ DIRECTORY_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, directory); ++ runtimePluginEntrypointHandler.enter(Entrypoint.PLUGIN); ++ } catch (Exception e) { ++ // This should never happen, any errors that occur in this provider should instead be logged. ++ this.server.getLogger().log(Level.SEVERE, "Unknown error occurred while loading plugins through PluginManager.", e); ++ } ++ ++ return runtimePluginEntrypointHandler.getPluginProviderStorage().getLoaded().toArray(new JavaPlugin[0]); ++ } ++ ++ // Plugins are disabled in order like this inorder to "rougly" prevent ++ // their dependencies unloading first. But, eh. ++ public void disablePlugins() { ++ Plugin[] plugins = this.getPlugins(); ++ for (int i = plugins.length - 1; i >= 0; i--) { ++ this.disablePlugin(plugins[i]); ++ } ++ } ++ ++ public void clearPlugins() { ++ synchronized (this) { ++ this.disablePlugins(); ++ this.plugins.clear(); ++ this.lookupNames.clear(); ++ } ++ } ++ ++ public synchronized void enablePlugin(@NotNull Plugin plugin) { ++ if (plugin.isEnabled()) { ++ return; ++ } ++ ++ if (plugin.getPluginMeta() instanceof PluginDescriptionFile) { ++ List<Command> bukkitCommands = PluginCommandYamlParser.parse(plugin); ++ ++ if (!bukkitCommands.isEmpty()) { ++ this.commandMap.registerAll(plugin.getPluginMeta().getName(), bukkitCommands); ++ } ++ } ++ ++ try { ++ String enableMsg = "Enabling " + plugin.getPluginMeta().getDisplayName(); ++ if (plugin.getPluginMeta() instanceof PluginDescriptionFile descriptionFile && CraftMagicNumbers.isLegacy(descriptionFile)) { ++ enableMsg += "*"; ++ } ++ plugin.getLogger().info(enableMsg); ++ ++ JavaPlugin jPlugin = (JavaPlugin) plugin; ++ ++ if (jPlugin.getClass().getClassLoader() instanceof ConfiguredPluginClassLoader classLoader) { // Paper ++ if (PaperClassLoaderStorage.instance().registerUnsafePlugin(classLoader)) { ++ this.server.getLogger().log(Level.WARNING, "Enabled plugin with unregistered ConfiguredPluginClassLoader " + plugin.getPluginMeta().getDisplayName()); ++ } ++ } // Paper ++ ++ try { ++ jPlugin.setEnabled(true); ++ } catch (Throwable ex) { ++ this.server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getPluginMeta().getDisplayName() + " (Is it up to date?)", ex); ++ // Paper start - Disable plugins that fail to load ++ this.server.getPluginManager().disablePlugin(jPlugin); ++ return; ++ // Paper end ++ } ++ ++ // Perhaps abort here, rather than continue going, but as it stands, ++ // an abort is not possible the way it's currently written ++ this.server.getPluginManager().callEvent(new PluginEnableEvent(plugin)); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while enabling " ++ + plugin.getPluginMeta().getDisplayName() + " (Is it up to date?)", ex, plugin); ++ } ++ ++ HandlerList.bakeAll(); ++ } ++ ++ public synchronized void disablePlugin(@NotNull Plugin plugin) { ++ if (!(plugin instanceof JavaPlugin javaPlugin)) { ++ throw new IllegalArgumentException("Only expects java plugins."); ++ } ++ if (!plugin.isEnabled()) { ++ return; ++ } ++ ++ String pluginName = plugin.getPluginMeta().getDisplayName(); ++ ++ try { ++ plugin.getLogger().info("Disabling %s".formatted(pluginName)); ++ ++ this.server.getPluginManager().callEvent(new PluginDisableEvent(plugin)); ++ ++ javaPlugin.setEnabled(false); ++ ++ ClassLoader classLoader = plugin.getClass().getClassLoader(); ++ if (classLoader instanceof ConfiguredPluginClassLoader configuredPluginClassLoader) { ++ try { ++ configuredPluginClassLoader.close(); ++ } catch (IOException ex) { ++ this.server.getLogger().log(Level.WARNING, "Error closing the classloader for '" + pluginName + "'", ex); // Paper - log exception ++ } ++ // Remove from the classloader pool inorder to prevent plugins from trying ++ // to access classes ++ PaperClassLoaderStorage.instance().unregisterClassloader(configuredPluginClassLoader); ++ } ++ ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while disabling " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ try { ++ this.server.getScheduler().cancelTasks(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ try { ++ this.server.getServicesManager().unregisterAll(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while unregistering services for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ try { ++ HandlerList.unregisterAll(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while unregistering events for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ try { ++ this.server.getMessenger().unregisterIncomingPluginChannel(plugin); ++ this.server.getMessenger().unregisterOutgoingPluginChannel(plugin); ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while unregistering plugin channels for " ++ + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ try { ++ for (World world : this.server.getWorlds()) { ++ world.removePluginChunkTickets(plugin); ++ } ++ } catch (Throwable ex) { ++ this.handlePluginException("Error occurred (in the plugin loader) while removing chunk tickets for " + pluginName + " (Is it up to date?)", ex, plugin); // Paper ++ } ++ ++ } ++ ++ // TODO: Implement event part in future patch (paper patch move up, this patch is lower) ++ private void handlePluginException(String msg, Throwable ex, Plugin plugin) { ++ Bukkit.getServer().getLogger().log(Level.SEVERE, msg, ex); ++ this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerPluginEnableDisableException(msg, ex, plugin))); ++ } ++ ++ public boolean isTransitiveDepend(@NotNull PluginMeta plugin, @NotNull PluginMeta depend) { ++ return this.context.isTransitiveDependency(plugin, depend); ++ } ++ ++ public boolean hasDependency(String pluginIdentifier) { ++ return this.getPlugin(pluginIdentifier) != null; ++ } ++ ++ // Debug only ++ @ApiStatus.Internal ++ public MutableGraph<String> getDependencyGraph() { ++ return this.dependencyGraph; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import com.google.common.graph.MutableGraph; ++import io.papermc.paper.plugin.PermissionManager; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import org.bukkit.Bukkit; ++import org.bukkit.Server; ++import org.bukkit.command.CommandMap; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.event.Event; ++import org.bukkit.event.EventPriority; ++import org.bukkit.event.Listener; ++import org.bukkit.permissions.Permissible; ++import org.bukkit.permissions.Permission; ++import org.bukkit.plugin.EventExecutor; ++import org.bukkit.plugin.InvalidDescriptionException; ++import org.bukkit.plugin.InvalidPluginException; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.PluginLoader; ++import org.bukkit.plugin.PluginManager; ++import org.bukkit.plugin.SimplePluginManager; ++import org.bukkit.plugin.UnknownDependencyException; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.File; ++import java.util.List; ++import java.util.Set; ++ ++public class PaperPluginManagerImpl implements PluginManager, DependencyContext { ++ ++ private final PaperPluginInstanceManager instanceManager; ++ private final PaperEventManager paperEventManager; ++ private PermissionManager permissionManager; ++ ++ public PaperPluginManagerImpl(Server server, CommandMap commandMap, @Nullable SimplePluginManager permissionManager) { ++ this.instanceManager = new PaperPluginInstanceManager(this, commandMap, server); ++ this.paperEventManager = new PaperEventManager(server); ++ ++ if (permissionManager == null) { ++ this.permissionManager = new NormalPaperPermissionManager(); ++ } else { ++ this.permissionManager = new StupidSPMPermissionManagerWrapper(permissionManager); // TODO: See comment when SimplePermissionManager is removed ++ } ++ } ++ ++ // REMOVE THIS WHEN SimplePluginManager is removed. ++ // Just cast and use Bukkit.getServer().getPluginManager() ++ public static PaperPluginManagerImpl getInstance() { ++ return ((CraftServer) (Bukkit.getServer())).paperPluginManager; ++ } ++ ++ // Plugin Manipulation ++ ++ @Override ++ public @Nullable Plugin getPlugin(@NotNull String name) { ++ return this.instanceManager.getPlugin(name); ++ } ++ ++ @Override ++ public @NotNull Plugin[] getPlugins() { ++ return this.instanceManager.getPlugins(); ++ } ++ ++ @Override ++ public boolean isPluginEnabled(@NotNull String name) { ++ return this.instanceManager.isPluginEnabled(name); ++ } ++ ++ @Override ++ public boolean isPluginEnabled(@Nullable Plugin plugin) { ++ return this.instanceManager.isPluginEnabled(plugin); ++ } ++ ++ public void loadPlugin(Plugin plugin) { ++ this.instanceManager.loadPlugin(plugin); ++ } ++ ++ @Override ++ public @Nullable Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, InvalidDescriptionException, UnknownDependencyException { ++ return this.instanceManager.loadPlugin(file.toPath()); ++ } ++ ++ @Override ++ public @NotNull Plugin[] loadPlugins(@NotNull File directory) { ++ return this.instanceManager.loadPlugins(directory.toPath()); ++ } ++ ++ @Override ++ public void disablePlugins() { ++ this.instanceManager.disablePlugins(); ++ } ++ ++ @Override ++ public synchronized void clearPlugins() { ++ this.instanceManager.clearPlugins(); ++ this.permissionManager.clearPermissions(); ++ this.paperEventManager.clearEvents(); ++ } ++ ++ @Override ++ public void enablePlugin(@NotNull Plugin plugin) { ++ this.instanceManager.enablePlugin(plugin); ++ } ++ ++ @Override ++ public void disablePlugin(@NotNull Plugin plugin) { ++ this.instanceManager.disablePlugin(plugin); ++ } ++ ++ @Override ++ public boolean isTransitiveDependency(PluginMeta pluginMeta, PluginMeta dependencyConfig) { ++ return this.instanceManager.isTransitiveDepend(pluginMeta, dependencyConfig); ++ } ++ ++ @Override ++ public boolean hasDependency(String pluginIdentifier) { ++ return this.instanceManager.hasDependency(pluginIdentifier); ++ } ++ ++ // Event manipulation ++ ++ @Override ++ public void callEvent(@NotNull Event event) throws IllegalStateException { ++ this.paperEventManager.callEvent(event); ++ } ++ ++ @Override ++ public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) { ++ this.paperEventManager.registerEvents(listener, plugin); ++ } ++ ++ @Override ++ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin) { ++ this.paperEventManager.registerEvent(event, listener, priority, executor, plugin); ++ } ++ ++ @Override ++ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin, boolean ignoreCancelled) { ++ this.paperEventManager.registerEvent(event, listener, priority, executor, plugin, ignoreCancelled); ++ } ++ ++ // Permission manipulation ++ ++ @Override ++ public @Nullable Permission getPermission(@NotNull String name) { ++ return this.permissionManager.getPermission(name); ++ } ++ ++ @Override ++ public void addPermission(@NotNull Permission perm) { ++ this.permissionManager.addPermission(perm); ++ } ++ ++ @Override ++ public void removePermission(@NotNull Permission perm) { ++ this.permissionManager.removePermission(perm); ++ } ++ ++ @Override ++ public void removePermission(@NotNull String name) { ++ this.permissionManager.removePermission(name); ++ } ++ ++ @Override ++ public @NotNull Set<Permission> getDefaultPermissions(boolean op) { ++ return this.permissionManager.getDefaultPermissions(op); ++ } ++ ++ @Override ++ public void recalculatePermissionDefaults(@NotNull Permission perm) { ++ this.permissionManager.recalculatePermissionDefaults(perm); ++ } ++ ++ @Override ++ public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) { ++ this.permissionManager.subscribeToPermission(permission, permissible); ++ } ++ ++ @Override ++ public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) { ++ this.permissionManager.unsubscribeFromPermission(permission, permissible); ++ } ++ ++ @Override ++ public @NotNull Set<Permissible> getPermissionSubscriptions(@NotNull String permission) { ++ return this.permissionManager.getPermissionSubscriptions(permission); ++ } ++ ++ @Override ++ public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ this.permissionManager.subscribeToDefaultPerms(op, permissible); ++ } ++ ++ @Override ++ public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) { ++ this.permissionManager.unsubscribeFromDefaultPerms(op, permissible); ++ } ++ ++ @Override ++ public @NotNull Set<Permissible> getDefaultPermSubscriptions(boolean op) { ++ return this.permissionManager.getDefaultPermSubscriptions(op); ++ } ++ ++ @Override ++ public @NotNull Set<Permission> getPermissions() { ++ return this.permissionManager.getPermissions(); ++ } ++ ++ @Override ++ public void addPermissions(@NotNull List<Permission> perm) { ++ this.permissionManager.addPermissions(perm); ++ } ++ ++ @Override ++ public void clearPermissions() { ++ this.permissionManager.clearPermissions(); ++ } ++ ++ @Override ++ public void overridePermissionManager(@NotNull Plugin plugin, @Nullable PermissionManager permissionManager) { ++ this.permissionManager = permissionManager; ++ } ++ ++ // Etc ++ ++ @Override ++ public boolean useTimings() { ++ return co.aikar.timings.Timings.isTimingsEnabled(); ++ } ++ ++ @Override ++ public void registerInterface(@NotNull Class<? extends PluginLoader> loader) throws IllegalArgumentException { ++ throw new UnsupportedOperationException(); ++ } ++ ++ public MutableGraph<String> getInstanceManagerGraph() { ++ return instanceManager.getDependencyGraph(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/RuntimePluginEntrypointHandler.java b/src/main/java/io/papermc/paper/plugin/manager/RuntimePluginEntrypointHandler.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/RuntimePluginEntrypointHandler.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import com.destroystokyo.paper.util.SneakyThrow; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.EntrypointHandler; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.storage.ProviderStorage; ++import org.bukkit.plugin.InvalidPluginException; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++ ++/** ++ * Used for loading plugins during runtime, only supporting providers that are plugins. ++ * This is only used for the plugin manager, as it only allows plugins to be ++ * registered to a provider storage. ++ */ ++class RuntimePluginEntrypointHandler<T extends ProviderStorage<JavaPlugin>> implements EntrypointHandler { ++ ++ private final T providerStorage; ++ ++ RuntimePluginEntrypointHandler(T providerStorage) { ++ this.providerStorage = providerStorage; ++ } ++ ++ @Override ++ public <T> void register(Entrypoint<T> entrypoint, PluginProvider<T> provider) { ++ if (!entrypoint.equals(Entrypoint.PLUGIN)) { ++ SneakyThrow.sneaky(new InvalidPluginException("Plugin cannot register entrypoints other than PLUGIN during runtime. Tried registering %s!".formatted(entrypoint))); ++ // We have to throw an invalid plugin exception for legacy reasons ++ } ++ ++ this.providerStorage.register((PluginProvider<JavaPlugin>) provider); ++ } ++ ++ @Override ++ public void enter(Entrypoint<?> entrypoint) { ++ if (entrypoint != Entrypoint.PLUGIN) { ++ throw new IllegalArgumentException("Only plugin entrypoint supported"); ++ } ++ this.providerStorage.enter(); ++ } ++ ++ @NotNull ++ public T getPluginProviderStorage() { ++ return this.providerStorage; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import com.destroystokyo.paper.util.SneakyThrow; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; ++import io.papermc.paper.plugin.storage.ServerPluginProviderStorage; ++import org.bukkit.plugin.InvalidPluginException; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.UnknownDependencyException; ++import org.bukkit.plugin.java.JavaPlugin; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Optional; ++ ++/** ++ * Used for registering a single plugin provider. ++ * This has special behavior in that some errors are thrown instead of logged. ++ */ ++class SingularRuntimePluginProviderStorage extends ServerPluginProviderStorage { ++ ++ private PluginProvider<JavaPlugin> lastProvider; ++ private JavaPlugin singleLoaded; ++ ++ @Override ++ public void register(PluginProvider<JavaPlugin> provider) { ++ super.register(provider); ++ if (this.lastProvider != null) { ++ SneakyThrow.sneaky(new InvalidPluginException("Plugin registered two JavaPlugins")); ++ } ++ if (provider instanceof PaperPluginParent.PaperServerPluginProvider) { ++ throw new IllegalStateException("Cannot register paper plugins during runtime!"); ++ } ++ this.lastProvider = provider; ++ // Register the provider into the server entrypoint, this allows it to show in /plugins correctly. ++ // Generally it might be better in the future to make a separate storage, as putting it into the entrypoint handlers doesn't make much sense. ++ LaunchEntryPointHandler.INSTANCE.register(Entrypoint.PLUGIN, provider); ++ } ++ ++ @Override ++ public void enter() { ++ PluginProvider<JavaPlugin> provider = this.lastProvider; ++ if (provider == null) { ++ return; ++ } ++ ++ // Manually validate dependencies, LEGACY BEHAVIOR. ++ // Normally it is logged, but manually adding one plugin will cause it to actually throw exceptions. ++ PluginDescriptionFile descriptionFile = (PluginDescriptionFile) provider.getMeta(); ++ List<String> missingDependencies = new ArrayList<>(); ++ for (String dependency : descriptionFile.getDepend()) { ++ if (!PaperPluginManagerImpl.getInstance().isPluginEnabled(dependency)) { ++ missingDependencies.add(dependency); ++ } ++ } ++ if (!missingDependencies.isEmpty()) { ++ throw new UnknownDependencyException(missingDependencies, provider.getFileName().toString()); ++ } ++ ++ // Go through normal plugin loading logic ++ super.enter(); ++ } ++ ++ @Override ++ public void processProvided(JavaPlugin provided) { ++ super.processProvided(provided); ++ this.singleLoaded = provided; ++ } ++ ++ @Override ++ public boolean exitOnCycleDependencies() { ++ return false; ++ } ++ ++ public Optional<JavaPlugin> getSingleLoaded() { ++ return Optional.ofNullable(this.singleLoaded); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java b/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.manager; ++ ++import org.bukkit.permissions.Permissible; ++import org.bukkit.permissions.Permission; ++import org.bukkit.plugin.SimplePluginManager; ++ ++import java.util.Map; ++import java.util.Set; ++ ++/* ++This is actually so cursed I hate it. ++We need to wrap these in fields as people override the fields, so we need to access them lazily at all times. ++// TODO: When SimplePluginManager is GONE remove this and cleanup the PaperPermissionManager to use actual fields. ++ */ ++class StupidSPMPermissionManagerWrapper extends PaperPermissionManager { ++ ++ private final SimplePluginManager simplePluginManager; ++ ++ public StupidSPMPermissionManagerWrapper(SimplePluginManager simplePluginManager) { ++ this.simplePluginManager = simplePluginManager; ++ } ++ ++ @Override ++ public Map<String, Permission> permissions() { ++ return this.simplePluginManager.permissions; ++ } ++ ++ @Override ++ public Map<Boolean, Set<Permission>> defaultPerms() { ++ return this.simplePluginManager.defaultPerms; ++ } ++ ++ @Override ++ public Map<String, Map<Permissible, Boolean>> permSubs() { ++ return this.simplePluginManager.permSubs; ++ } ++ ++ @Override ++ public Map<Boolean, Map<Permissible, Boolean>> defSubs() { ++ return this.simplePluginManager.defSubs; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java b/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Path; ++import java.util.jar.JarFile; ++import java.util.logging.Logger; ++ ++/** ++ * PluginProviders are created by a {@link io.papermc.paper.plugin.provider.source.ProviderSource}, ++ * which is loaded into an {@link io.papermc.paper.plugin.entrypoint.EntrypointHandler}. ++ * <p> ++ * A PluginProvider is responsible for providing part of a plugin, whether it's a Bootstrapper or Server Plugin. ++ * The point of this class is to be able to create the actual instance later, as at the time this is created the server ++ * may be missing some key parts. For example, the Bukkit singleton will not be initialized yet, therefor we need to ++ * have a PluginServerProvider load the server plugin later. ++ * <p> ++ * Plugin providers are currently not exposed in any way of the api. It is preferred that this stays this way, ++ * as providers are only needed for initialization. ++ * ++ * @param <T> provider type ++ */ ++@ApiStatus.Internal ++public interface PluginProvider<T> { ++ ++ @NotNull ++ Path getSource(); ++ ++ default Path getFileName() { ++ return this.getSource().getFileName(); ++ } ++ ++ default Path getParentSource() { ++ return this.getSource().getParent(); ++ } ++ ++ JarFile file(); ++ ++ T createInstance(); ++ ++ PluginMeta getMeta(); ++ ++ Logger getLogger(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider; ++ ++import org.jetbrains.annotations.ApiStatus; ++ ++/** ++ * This is used for the /plugins command, where it will look in the {@link io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler} and ++ * use the provider statuses to determine the color. ++ */ ++@ApiStatus.Internal ++public enum ProviderStatus { ++ INITIALIZED, ++ ERRORED, ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/ProviderStatusHolder.java b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatusHolder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatusHolder.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider; ++ ++/** ++ * This is used to mark that a plugin provider is able to hold a status for the /plugins command. ++ */ ++public interface ProviderStatusHolder { ++ ++ ProviderStatus getLastProvidedStatus(); ++ ++ void setStatus(ProviderStatus status); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/FlattenedResolver.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/FlattenedResolver.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/FlattenedResolver.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration; ++ ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.spongepowered.configurate.objectmapping.meta.NodeResolver; ++ ++import java.lang.annotation.ElementType; ++import java.lang.annotation.Retention; ++import java.lang.annotation.RetentionPolicy; ++import java.lang.annotation.Target; ++import java.lang.reflect.AnnotatedElement; ++ ++@Retention(RetentionPolicy.RUNTIME) ++@Target(ElementType.FIELD) ++public @interface FlattenedResolver { ++ ++ final class Factory implements NodeResolver.Factory { ++ ++ @Override ++ public @Nullable NodeResolver make(String name, AnnotatedElement element) { ++ if (element.isAnnotationPresent(FlattenedResolver.class)) { ++ return (node) -> node; ++ } else { ++ return null; ++ } ++ } ++ } ++ ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration; ++ ++import com.google.common.collect.ImmutableList; ++import io.leangen.geantyref.TypeToken; ++import io.papermc.paper.configuration.constraint.Constraint; ++import io.papermc.paper.configuration.serializer.ComponentSerializer; ++import io.papermc.paper.configuration.serializer.EnumValueSerializer; ++import io.papermc.paper.configuration.serializer.collections.MapSerializer; ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.provider.configuration.serializer.ImmutableListSerializer; ++import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer; ++import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints; ++import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; ++import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.bukkit.plugin.PluginLoadOrder; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import org.spongepowered.configurate.CommentedConfigurationNode; ++import org.spongepowered.configurate.ConfigurateException; ++import org.spongepowered.configurate.loader.HeaderMode; ++import org.spongepowered.configurate.objectmapping.ConfigSerializable; ++import org.spongepowered.configurate.objectmapping.ObjectMapper; ++import org.spongepowered.configurate.objectmapping.meta.Required; ++import org.spongepowered.configurate.yaml.NodeStyle; ++import org.spongepowered.configurate.yaml.YamlConfigurationLoader; ++ ++import java.io.BufferedReader; ++import java.util.List; ++ ++@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) ++@ConfigSerializable ++public class PaperPluginMeta implements PluginMeta { ++ ++ @PluginConfigConstraints.PluginName ++ @Required ++ private String name; ++ @Required ++ @PluginConfigConstraints.PluginNameSpace ++ private String main; ++ @PluginConfigConstraints.PluginNameSpace ++ private String bootstrapper; ++ @PluginConfigConstraints.PluginNameSpace ++ private String loader; ++ private List<DependencyConfiguration> dependencies = List.of(); ++ private List<String> loadBefore = List.of(); ++ private List<String> provides = List.of(); ++ private boolean hasOpenClassloader = false; ++ @Required ++ private String version; ++ private String description; ++ private List<String> authors = List.of(); ++ private List<String> contributors = List.of(); ++ private String website; ++ private String prefix; ++ private PluginLoadOrder load = PluginLoadOrder.POSTWORLD; ++ @FlattenedResolver ++ private PermissionConfiguration permissionConfiguration = new PermissionConfiguration(PermissionDefault.OP, List.of()); ++ @Required ++ @PluginConfigConstraints.PluginVersion ++ private String apiVersion; ++ ++ private transient String displayName; ++ ++ public PaperPluginMeta() { ++ } ++ ++ public static PaperPluginMeta create(BufferedReader reader) throws ConfigurateException { ++ YamlConfigurationLoader loader = YamlConfigurationLoader.builder() ++ .indent(2) ++ .nodeStyle(NodeStyle.BLOCK) ++ .headerMode(HeaderMode.NONE) ++ .source(() -> reader) ++ .defaultOptions((options) -> { ++ ++ return options.serializers((serializers) -> { ++ serializers ++ .register(new EnumValueSerializer()) ++ .register(MapSerializer.TYPE, new MapSerializer(false)) ++ .register(new TypeToken<>() { ++ }, new ImmutableListSerializer()) ++ .register(PermissionConfiguration.class, PermissionConfigurationSerializer.SERIALIZER) ++ .register(new ComponentSerializer()) ++ .registerAnnotatedObjects( ++ ObjectMapper.factoryBuilder() ++ .addConstraint(Constraint.class, new Constraint.Factory()) ++ .addConstraint(PluginConfigConstraints.PluginName.class, String.class, new PluginConfigConstraints.PluginName.Factory()) ++ .addConstraint(PluginConfigConstraints.PluginVersion.class, String.class, new PluginConfigConstraints.PluginVersion.Factory()) ++ .addConstraint(PluginConfigConstraints.PluginNameSpace.class, String.class, new PluginConfigConstraints.PluginNameSpace.Factory()) ++ .addNodeResolver(new FlattenedResolver.Factory()) ++ .build() ++ ); ++ ++ }); ++ }) ++ .build(); ++ CommentedConfigurationNode node = loader.load(); ++ PaperPluginMeta pluginConfiguration = node.require(PaperPluginMeta.class); ++ ++ if (!node.node("author").virtual()) { ++ pluginConfiguration.authors = ImmutableList.<String>builder() ++ .addAll(pluginConfiguration.authors) ++ .add(node.node("author").getString()) ++ .build(); ++ } ++ ++ pluginConfiguration.displayName = pluginConfiguration.name.replace('_', ' '); ++ ++ return pluginConfiguration; ++ } ++ ++ @Override ++ public @NotNull String getName() { ++ return this.name; ++ } ++ ++ @Override ++ public @NotNull String getMainClass() { ++ return this.main; ++ } ++ ++ @Override ++ public @NotNull String getVersion() { ++ return this.version; ++ } ++ ++ @Override ++ public @NotNull String getDisplayName() { ++ return this.displayName; ++ } ++ ++ @Override ++ public @Nullable String getLoggerPrefix() { ++ return this.prefix; ++ } ++ ++ @Override ++ public @NotNull List<String> getPluginDependencies() { ++ return this.dependencies.stream().filter((dependency) -> dependency.required() && !dependency.bootstrap()).map(DependencyConfiguration::name).toList(); ++ } ++ ++ @Override ++ public @NotNull List<String> getPluginSoftDependencies() { ++ return this.dependencies.stream().filter((dependency) -> !dependency.required() && !dependency.bootstrap()).map(DependencyConfiguration::name).toList(); ++ } ++ ++ @Override ++ public @NotNull List<String> getLoadBeforePlugins() { ++ return this.loadBefore; ++ } ++ ++ @Override ++ public @NotNull PluginLoadOrder getLoadOrder() { ++ return this.load; ++ } ++ ++ @Override ++ public @NotNull String getDescription() { ++ return this.description; ++ } ++ ++ @Override ++ public @NotNull List<String> getAuthors() { ++ return this.authors; ++ } ++ ++ @Override ++ public @NotNull List<String> getContributors() { ++ return this.contributors; ++ } ++ ++ @Override ++ public String getWebsite() { ++ return this.website; ++ } ++ ++ @Override ++ public @NotNull List<Permission> getPermissions() { ++ return this.permissionConfiguration.permissions(); ++ } ++ ++ @Override ++ public @NotNull PermissionDefault getPermissionDefault() { ++ return this.permissionConfiguration.defaultPerm(); ++ } ++ ++ @Override ++ public @NotNull String getAPIVersion() { ++ return this.apiVersion; ++ } ++ ++ @Override ++ public @NotNull List<String> getProvidedPlugins() { ++ return this.provides; ++ } ++ ++ public String getBootstrapper() { ++ return this.bootstrapper; ++ } ++ ++ public String getLoader() { ++ return this.loader; ++ } ++ ++ public boolean hasOpenClassloader() { ++ return this.hasOpenClassloader; ++ } ++ ++ public List<DependencyConfiguration> getDependencies() { ++ return dependencies; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.serializer; ++ ++import com.google.common.collect.ImmutableCollection; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.spongepowered.configurate.ConfigurationNode; ++import org.spongepowered.configurate.ConfigurationOptions; ++import org.spongepowered.configurate.serialize.SerializationException; ++import org.spongepowered.configurate.serialize.TypeSerializer; ++import org.spongepowered.configurate.util.CheckedConsumer; ++ ++import java.lang.reflect.Type; ++import java.util.Collection; ++import java.util.Collections; ++import java.util.List; ++ ++@SuppressWarnings("unchecked") ++public abstract class ImmutableCollectionSerializer<B extends ImmutableCollection.Builder<?>, T extends Collection<?>> implements TypeSerializer<T> { ++ ++ protected ImmutableCollectionSerializer() { ++ } ++ ++ @Override ++ public final T deserialize(final Type type, final ConfigurationNode node) throws SerializationException { ++ final Type entryType = this.elementType(type); ++ final @Nullable TypeSerializer<?> entrySerial = node.options().serializers().get(entryType); ++ if (entrySerial == null) { ++ throw new SerializationException(node, entryType, "No applicable type serializer for type"); ++ } ++ ++ if (node.isList()) { ++ final List<? extends ConfigurationNode> values = node.childrenList(); ++ final B builder = this.createNew(values.size()); ++ for (ConfigurationNode value : values) { ++ try { ++ this.deserializeSingle(builder, entrySerial.deserialize(entryType, value)); ++ } catch (final SerializationException ex) { ++ ex.initPath(value::path); ++ throw ex; ++ } ++ } ++ return (T) builder.build(); ++ } else { ++ final @Nullable Object unwrappedVal = node.raw(); ++ if (unwrappedVal != null) { ++ final B builder = this.createNew(1); ++ this.deserializeSingle(builder, entrySerial.deserialize(entryType, node)); ++ return (T) builder.build(); ++ } ++ } ++ return this.emptyValue(type, null); ++ } ++ ++ @SuppressWarnings({"unchecked", "rawtypes"}) ++ @Override ++ public final void serialize(final Type type, final @Nullable T obj, final ConfigurationNode node) throws SerializationException { ++ final Type entryType = this.elementType(type); ++ final @Nullable TypeSerializer entrySerial = node.options().serializers().get(entryType); ++ if (entrySerial == null) { ++ throw new SerializationException(node, entryType, "No applicable type serializer for type"); ++ } ++ ++ node.raw(Collections.emptyList()); ++ if (obj != null) { ++ this.forEachElement(obj, el -> { ++ final ConfigurationNode child = node.appendListNode(); ++ try { ++ entrySerial.serialize(entryType, el, child); ++ } catch (final SerializationException ex) { ++ ex.initPath(child::path); ++ throw ex; ++ } ++ }); ++ } ++ } ++ ++ @SuppressWarnings({"unchecked"}) ++ @Override ++ public @Nullable T emptyValue(final Type specificType, final ConfigurationOptions options) { ++ return (T) this.createNew(0).build(); ++ } ++ ++ protected abstract Type elementType(Type containerType) throws SerializationException; ++ ++ protected abstract B createNew(int size); ++ ++ protected abstract void forEachElement(T collection, CheckedConsumer<Object, SerializationException> action) throws SerializationException; ++ ++ protected abstract void deserializeSingle(B builder, @Nullable Object deserialized) throws SerializationException; ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableListSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableListSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableListSerializer.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.serializer; ++ ++import com.google.common.collect.ImmutableList; ++import org.checkerframework.checker.nullness.qual.Nullable; ++import org.spongepowered.configurate.serialize.SerializationException; ++import org.spongepowered.configurate.util.CheckedConsumer; ++ ++import java.lang.reflect.ParameterizedType; ++import java.lang.reflect.Type; ++import java.util.List; ++ ++public class ImmutableListSerializer extends ImmutableCollectionSerializer<ImmutableList.Builder<?>, List<?>> { ++ ++ @Override ++ protected Type elementType(Type containerType) throws SerializationException { ++ if (!(containerType instanceof ParameterizedType)) { ++ throw new SerializationException(containerType, "Raw types are not supported for collections"); ++ } ++ return ((ParameterizedType) containerType).getActualTypeArguments()[0]; ++ } ++ ++ @Override ++ protected ImmutableList.Builder<?> createNew(int size) { ++ return ImmutableList.builderWithExpectedSize(size); ++ } ++ ++ @Override ++ protected void forEachElement(List<?> collection, CheckedConsumer<Object, SerializationException> action) throws SerializationException { ++ for (Object obj : collection) { ++ action.accept(obj); ++ } ++ } ++ ++ @SuppressWarnings({"unchecked", "rawtypes"}) ++ @Override ++ protected void deserializeSingle(ImmutableList.Builder<?> builder, @Nullable Object deserialized) throws SerializationException { ++ if (deserialized == null) { ++ return; ++ } ++ ++ ((ImmutableList.Builder) builder).add(deserialized); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.serializer; ++ ++import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.spongepowered.configurate.ConfigurationNode; ++import org.spongepowered.configurate.serialize.SerializationException; ++import org.spongepowered.configurate.serialize.TypeSerializer; ++ ++import java.lang.reflect.Type; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.Map; ++ ++public class PermissionConfigurationSerializer { ++ ++ public static final Serializer SERIALIZER = new Serializer(); ++ ++ private static final class Serializer implements TypeSerializer<PermissionConfiguration> { ++ private Serializer() { ++ super(); ++ } ++ ++ @Override ++ public PermissionConfiguration deserialize(Type type, ConfigurationNode node) throws SerializationException { ++ Map<?, ?> map = (Map<?, ?>) node.node("permissions").raw(); ++ ++ PermissionDefault permissionDefault; ++ ConfigurationNode permNode = node.node("defaultPerm"); ++ if (permNode.virtual()) { ++ permissionDefault = PermissionDefault.OP; ++ } else { ++ permissionDefault = PermissionDefault.getByName(permNode.getString()); ++ } ++ ++ List<Permission> result = new ArrayList<>(); ++ if (map != null) { ++ for (Map.Entry<?, ?> entry : map.entrySet()) { ++ try { ++ result.add(Permission.loadPermission(entry.getKey().toString(), (Map<?, ?>) entry.getValue(), permissionDefault, result)); ++ } catch (Throwable ex) { ++ throw new SerializationException(null, "Error loading permission %s".formatted(entry.getKey()), ex); ++ } ++ } ++ } ++ ++ return new PermissionConfiguration(permissionDefault, List.copyOf(result)); ++ } ++ ++ @Override ++ public void serialize(Type type, @org.checkerframework.checker.nullness.qual.Nullable PermissionConfiguration obj, ConfigurationNode node) throws SerializationException { ++ ++ } ++ ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/constraints/PluginConfigConstraints.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/constraints/PluginConfigConstraints.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/constraints/PluginConfigConstraints.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.serializer.constraints; ++ ++import io.papermc.paper.plugin.util.NamespaceChecker; ++import org.spongepowered.configurate.objectmapping.meta.Constraint; ++import org.spongepowered.configurate.serialize.SerializationException; ++ ++import java.lang.annotation.Documented; ++import java.lang.annotation.ElementType; ++import java.lang.annotation.Retention; ++import java.lang.annotation.RetentionPolicy; ++import java.lang.annotation.Target; ++import java.lang.reflect.Type; ++import java.util.Locale; ++import java.util.Set; ++import java.util.regex.Pattern; ++ ++public final class PluginConfigConstraints { ++ ++ public static final Set<String> RESERVED_KEYS = Set.of("bukkit", "minecraft", "mojang", "spigot", "paper"); ++ public static final Set<String> VALID_PAPER_VERSIONS = Set.of("1.19"); ++ ++ @Documented ++ @Retention(RetentionPolicy.RUNTIME) ++ @Target(ElementType.FIELD) ++ public @interface PluginName { ++ ++ final class Factory implements Constraint.Factory<PluginName, String> { ++ ++ private static final Pattern VALID_NAME = Pattern.compile("^[A-Za-z\\d _.-]+$"); ++ ++ @Override ++ public Constraint<String> make(PluginName data, Type type) { ++ return value -> { ++ if (value != null) { ++ if (RESERVED_KEYS.contains(value.toLowerCase(Locale.ROOT))) { ++ throw new SerializationException("Restricted name, cannot use '%s' as a plugin name.".formatted(data)); ++ } else if (value.indexOf(' ') != -1) { ++ // For legacy reasons, the space condition has a separate exception message. ++ throw new SerializationException("Restricted name, cannot use 0x20 (space character) in a plugin name."); ++ } ++ ++ if (!VALID_NAME.matcher(value).matches()) { ++ throw new SerializationException("name '" + value + "' contains invalid characters."); ++ } ++ } ++ }; ++ } ++ } ++ } ++ ++ @Documented ++ @Retention(RetentionPolicy.RUNTIME) ++ @Target(ElementType.FIELD) ++ public @interface PluginNameSpace { ++ ++ final class Factory implements Constraint.Factory<PluginNameSpace, String> { ++ ++ @Override ++ public Constraint<String> make(PluginNameSpace data, Type type) { ++ return value -> { ++ if (value != null && !NamespaceChecker.isValidNameSpace(value)) { ++ throw new SerializationException("provided class '%s' is in an invalid namespace.".formatted(value)); ++ } ++ }; ++ } ++ } ++ } ++ ++ @Documented ++ @Retention(RetentionPolicy.RUNTIME) ++ @Target(ElementType.FIELD) ++ public @interface PluginVersion { ++ ++ final class Factory implements Constraint.Factory<PluginVersion, String> { ++ ++ @Override ++ public Constraint<String> make(PluginVersion data, Type type) { ++ return value -> { ++ if (value != null && !VALID_PAPER_VERSIONS.contains(value)) { ++ throw new SerializationException("Provided plugin's version (%s) is not supported on this version.".formatted(value)); ++ } ++ }; ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/DependencyConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/DependencyConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/DependencyConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.type; ++ ++import org.spongepowered.configurate.objectmapping.ConfigSerializable; ++import org.spongepowered.configurate.objectmapping.meta.Required; ++ ++@ConfigSerializable ++public record DependencyConfiguration( ++ @Required String name, ++ boolean required, ++ boolean bootstrap ++) { ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.configuration.type; ++ ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.spongepowered.configurate.objectmapping.ConfigSerializable; ++ ++import java.util.List; ++ ++// Record components used for deserialization!!!! ++@ConfigSerializable ++public record PermissionConfiguration( ++ PermissionDefault defaultPerm, ++ List<Permission> permissions) { ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.source; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.entrypoint.EntrypointHandler; ++import org.slf4j.Logger; ++ ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.logging.Level; ++ ++/** ++ * Loads all plugin providers in the given directory. ++ */ ++public class DirectoryProviderSource extends FileProviderSource { ++ ++ public static final DirectoryProviderSource INSTANCE = new DirectoryProviderSource(); ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public DirectoryProviderSource() { ++ super("Directory '%s'"::formatted); ++ } ++ ++ @Override ++ public void registerProviders(EntrypointHandler entrypointHandler, Path context) throws Exception { ++ // Sym link happy, create file if missing. ++ if (!Files.isDirectory(context)) { ++ Files.createDirectories(context); ++ } ++ ++ Files.walk(context, 1).filter(Files::isRegularFile).forEach((path) -> { ++ try { ++ super.registerProviders(entrypointHandler, path); ++ } catch (IllegalArgumentException ignored) { ++ // Ignore initial argument exceptions ++ } catch (Exception e) { ++ LOGGER.error("Error loading plugin: " + e.getMessage(), e); ++ } ++ }); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.source; ++ ++import io.papermc.paper.plugin.PluginInitializerManager; ++import io.papermc.paper.plugin.entrypoint.EntrypointHandler; ++import io.papermc.paper.plugin.provider.type.PluginFileType; ++import org.bukkit.plugin.InvalidPluginException; ++import org.jetbrains.annotations.Nullable; ++ ++import java.io.File; ++import java.io.IOException; ++import java.nio.file.FileVisitResult; ++import java.nio.file.FileVisitor; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.nio.file.StandardCopyOption; ++import java.nio.file.attribute.BasicFileAttributes; ++import java.util.Set; ++import java.util.function.Function; ++import java.util.jar.JarFile; ++ ++/** ++ * Loads a plugin provider at the given plugin jar file path. ++ */ ++public class FileProviderSource implements ProviderSource<Path> { ++ ++ private final Function<Path, String> contextChecker; ++ ++ public FileProviderSource(Function<Path, String> contextChecker) { ++ this.contextChecker = contextChecker; ++ } ++ ++ @Override ++ public void registerProviders(EntrypointHandler entrypointHandler, Path context) throws Exception { ++ String source = this.contextChecker.apply(context); ++ ++ if (Files.notExists(context)) { ++ throw new IllegalArgumentException(source + " does not exist, cannot load a plugin from it!"); ++ } ++ ++ if (!Files.isRegularFile(context)) { ++ throw new IllegalArgumentException(source + " is not a file, cannot load a plugin from it!"); ++ } ++ ++ if (!context.getFileName().toString().endsWith(".jar")) { ++ throw new IllegalArgumentException(source + " is not a jar file, cannot load a plugin from it!"); ++ } ++ ++ try { ++ this.checkUpdate(context); ++ ++ JarFile file = new JarFile(context.toFile()); ++ PluginFileType<?,?> type = PluginFileType.guessType(file); ++ if (type == null) { ++ throw new IllegalArgumentException(source + " is not a valid plugin file, cannot load a plugin from it!"); ++ } ++ ++ type.register(entrypointHandler, file, context); ++ } catch (Exception exception) { ++ throw new RuntimeException(source + " failed to load!", exception); ++ } ++ } ++ ++ /** ++ * Replaces a plugin with a plugin of the same plugin name in the update folder. ++ * ++ * @param file ++ */ ++ private Path checkUpdate(Path file) throws Exception { ++ PluginInitializerManager pluginSystem = PluginInitializerManager.instance(); ++ if (!Files.isDirectory(pluginSystem.pluginUpdatePath())) { ++ return file; ++ } ++ ++ try { ++ String pluginName = this.getPluginName(file); ++ UpdateFileVisitor visitor = new UpdateFileVisitor(pluginName); ++ Files.walkFileTree(pluginSystem.pluginUpdatePath(), Set.of(), 1, visitor); ++ if (visitor.getValidPlugin() != null) { ++ Path updateLocation = visitor.getValidPlugin(); ++ ++ try { ++ Files.copy(updateLocation, file, StandardCopyOption.REPLACE_EXISTING); ++ } catch (IOException exception) { ++ throw new RuntimeException("Could not copy '" + updateLocation + "' to '" + file + "' in update plugin process", exception); ++ } ++ ++ // Idk what this is about, TODO ++ File newName = new File(file.toFile().getParentFile(), updateLocation.toFile().getName()); ++ file.toFile().renameTo(newName); ++ updateLocation.toFile().delete(); ++ } ++ } catch (Exception e) { ++ throw new InvalidPluginException(e); ++ } ++ return file; ++ } ++ ++ private String getPluginName(Path path) throws Exception { ++ JarFile file = new JarFile(path.toFile()); ++ PluginFileType<?, ?> type = PluginFileType.guessType(file); ++ if (type == null) { ++ throw new IllegalArgumentException(path + " is not a valid plugin file, cannot load a plugin from it!"); ++ } ++ ++ return type.getConfig(file).getName(); ++ } ++ ++ private class UpdateFileVisitor implements FileVisitor<Path> { ++ ++ private final String targetName; ++ @Nullable ++ private Path validPlugin; ++ ++ private UpdateFileVisitor(String targetName) { ++ this.targetName = targetName; ++ } ++ ++ @Override ++ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { ++ return FileVisitResult.CONTINUE; ++ } ++ ++ @Override ++ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { ++ try { ++ String updatePluginName = FileProviderSource.this.getPluginName(file); ++ if (this.targetName.equals(updatePluginName)) { ++ this.validPlugin = file; ++ return FileVisitResult.TERMINATE; ++ } ++ } catch (Exception e) { ++ // We failed to load this data for some reason, so, we'll skip over this ++ } ++ ++ ++ return FileVisitResult.CONTINUE; ++ } ++ ++ @Override ++ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { ++ return FileVisitResult.CONTINUE; ++ } ++ ++ @Override ++ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { ++ return FileVisitResult.CONTINUE; ++ } ++ ++ @Nullable ++ public Path getValidPlugin() { ++ return validPlugin; ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/PluginFlagProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/PluginFlagProviderSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/source/PluginFlagProviderSource.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.source; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.entrypoint.EntrypointHandler; ++import org.slf4j.Logger; ++ ++import java.io.File; ++import java.util.List; ++ ++/** ++ * Registers providers at the provided files in the add-plugin argument. ++ */ ++public class PluginFlagProviderSource implements ProviderSource<List<File>> { ++ ++ public static final PluginFlagProviderSource INSTANCE = new PluginFlagProviderSource(); ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ private final FileProviderSource providerSource = new FileProviderSource("File '%s' specified through 'add-plugin' argument"::formatted); ++ ++ @Override ++ public void registerProviders(EntrypointHandler entrypointHandler, List<File> context) { ++ for (File file : context) { ++ try { ++ this.providerSource.registerProviders(entrypointHandler, file.toPath()); ++ } catch (Exception e) { ++ LOGGER.error("Error loading plugin: " + e.getMessage(), e); ++ } ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/ProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/ProviderSource.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/source/ProviderSource.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.source; ++ ++import io.papermc.paper.plugin.entrypoint.EntrypointHandler; ++ ++/** ++ * A provider source is responsible for giving PluginTypes an EntrypointHandler for ++ * registering providers at. ++ * ++ * @param <C> context ++ */ ++public interface ProviderSource<C> { ++ ++ void registerProviders(EntrypointHandler entrypointHandler, C context) throws Throwable; ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java b/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import io.papermc.paper.plugin.entrypoint.Entrypoint; ++import io.papermc.paper.plugin.entrypoint.EntrypointHandler; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; ++import io.papermc.paper.plugin.provider.type.spigot.SpigotPluginProvider; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.jetbrains.annotations.Nullable; ++ ++import java.nio.file.Path; ++import java.util.List; ++import java.util.jar.JarEntry; ++import java.util.jar.JarFile; ++ ++/** ++ * This is where spigot/paper plugins are registered. ++ * This will get the jar and find a certain config file, create an object ++ * then registering it into a {@link EntrypointHandler} at a certain {@link Entrypoint}. ++ */ ++public abstract class PluginFileType<T, C extends PluginMeta> { ++ ++ public static final PluginFileType<PaperPluginParent, PaperPluginMeta> PAPER = new PluginFileType<>("paper-plugin.yml", PaperPluginParent.FACTORY) { ++ @Override ++ protected void register(EntrypointHandler entrypointHandler, PaperPluginParent parent) { ++ PaperPluginParent.PaperBootstrapProvider bootstrapPluginProvider = null; ++ if (parent.shouldCreateBootstrap()) { ++ bootstrapPluginProvider = parent.createBootstrapProvider(); ++ entrypointHandler.register(Entrypoint.BOOTSTRAPPER, bootstrapPluginProvider); ++ } ++ ++ entrypointHandler.register(Entrypoint.PLUGIN, parent.createPluginProvider(bootstrapPluginProvider)); ++ } ++ }; ++ public static final PluginFileType<SpigotPluginProvider, PluginDescriptionFile> SPIGOT = new PluginFileType<>("plugin.yml", SpigotPluginProvider.FACTORY) { ++ @Override ++ protected void register(EntrypointHandler entrypointHandler, SpigotPluginProvider provider) { ++ entrypointHandler.register(Entrypoint.PLUGIN, provider); ++ } ++ }; ++ ++ private static final List<PluginFileType<?, ?>> VALUES = List.of(PAPER, SPIGOT); ++ ++ private final String config; ++ private final PluginTypeFactory<T, C> factory; ++ ++ PluginFileType(String config, PluginTypeFactory<T, C> factory) { ++ this.config = config; ++ this.factory = factory; ++ } ++ ++ @Nullable ++ public static PluginFileType<?, ?> guessType(JarFile file) { ++ for (PluginFileType<?, ?> type : VALUES) { ++ JarEntry entry = file.getJarEntry(type.config); ++ if (entry != null) { ++ return type; ++ } ++ } ++ ++ return null; ++ } ++ ++ public T register(EntrypointHandler entrypointHandler, JarFile file, Path context) throws Exception { ++ C config = this.getConfig(file); ++ T provider = this.factory.build(file, config, context); ++ this.register(entrypointHandler, provider); ++ return provider; ++ } ++ ++ public C getConfig(JarFile file) throws Exception { ++ return this.factory.create(file, file.getJarEntry(this.config)); ++ } ++ ++ protected abstract void register(EntrypointHandler entrypointHandler, T provider); ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++ ++import java.nio.file.Path; ++import java.util.jar.JarEntry; ++import java.util.jar.JarFile; ++ ++/** ++ * A plugin type factory is responsible for building an object ++ * and config for a certain plugin type. ++ * ++ * @param <T> plugin provider type (may not be a plugin provider) ++ * @param <C> config type ++ */ ++public interface PluginTypeFactory<T, C extends PluginMeta> { ++ ++ T build(JarFile file, C configuration, Path source) throws Exception; ++ ++ C create(JarFile file, JarEntry config) throws Exception; ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type.paper; ++ ++import com.destroystokyo.paper.util.SneakyThrow; ++import io.papermc.paper.plugin.bootstrap.PluginProviderContext; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder; ++import io.papermc.paper.plugin.bootstrap.PluginBootstrap; ++import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl; ++import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.ProviderStatus; ++import io.papermc.paper.plugin.provider.ProviderStatusHolder; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import io.papermc.paper.plugin.provider.type.PluginTypeFactory; ++import io.papermc.paper.plugin.provider.util.ProviderUtil; ++import org.bukkit.Bukkit; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Path; ++import java.util.jar.JarFile; ++import java.util.logging.Logger; ++ ++public class PaperPluginParent { ++ ++ public static final PluginTypeFactory<PaperPluginParent, PaperPluginMeta> FACTORY = new PaperPluginProviderFactory(); ++ private final Path path; ++ private final JarFile jarFile; ++ private final PaperPluginMeta description; ++ private final PaperPluginClassLoader classLoader; ++ private final PluginProviderContext context; ++ private final Logger logger; ++ ++ public PaperPluginParent(Path path, JarFile jarFile, PaperPluginMeta description, PaperPluginClassLoader classLoader, PluginProviderContext context) { ++ this.path = path; ++ this.jarFile = jarFile; ++ this.description = description; ++ this.classLoader = classLoader; ++ this.context = context; ++ this.logger = context.getLogger(); ++ } ++ ++ public boolean shouldCreateBootstrap() { ++ return this.description.getBootstrapper() != null; ++ } ++ ++ public PaperBootstrapProvider createBootstrapProvider() { ++ return new PaperBootstrapProvider(); ++ } ++ ++ public PaperServerPluginProvider createPluginProvider(PaperBootstrapProvider provider) { ++ return new PaperServerPluginProvider(provider); ++ } ++ ++ public class PaperBootstrapProvider implements PluginProvider<PluginBootstrap>, ProviderStatusHolder, DependencyContextHolder { ++ ++ private ProviderStatus status; ++ private PluginBootstrap lastProvided; ++ ++ @Override ++ public @NotNull Path getSource() { ++ return PaperPluginParent.this.path; ++ } ++ ++ @Override ++ public JarFile file() { ++ return PaperPluginParent.this.jarFile; ++ } ++ ++ @Override ++ public PluginBootstrap createInstance() { ++ PluginBootstrap bootstrap = ProviderUtil.loadClass(PaperPluginParent.this.description.getBootstrapper(), ++ PluginBootstrap.class, PaperPluginParent.this.classLoader, () -> this.status = ProviderStatus.ERRORED); ++ this.status = ProviderStatus.INITIALIZED; ++ this.lastProvided = bootstrap; ++ return bootstrap; ++ } ++ ++ @Override ++ public PaperPluginMeta getMeta() { ++ return PaperPluginParent.this.description; ++ } ++ ++ @Override ++ public Logger getLogger() { ++ return PaperPluginParent.this.logger; ++ } ++ ++ @Override ++ public ProviderStatus getLastProvidedStatus() { ++ return this.status; ++ } ++ ++ @Override ++ public void setStatus(ProviderStatus status) { ++ this.status = status; ++ } ++ ++ public PluginBootstrap getLastProvided() { ++ return this.lastProvided; ++ } ++ ++ @Override ++ public void setContext(DependencyContext context) { ++ PaperPluginParent.this.classLoader.refreshClassloaderDependencyTree(context); ++ } ++ ++ @Override ++ public String toString() { ++ return "PaperBootstrapProvider{" + ++ "parent=" + PaperPluginParent.this + ++ "status=" + status + ++ ", lastProvided=" + lastProvided + ++ '}'; ++ } ++ } ++ ++ public class PaperServerPluginProvider implements PluginProvider<JavaPlugin>, ProviderStatusHolder, DependencyContextHolder { ++ ++ private final PaperBootstrapProvider bootstrapProvider; ++ ++ private ProviderStatus status; ++ ++ PaperServerPluginProvider(PaperBootstrapProvider bootstrapProvider) { ++ this.bootstrapProvider = bootstrapProvider; ++ } ++ ++ @Override ++ public @NotNull Path getSource() { ++ return PaperPluginParent.this.path; ++ } ++ ++ @Override ++ public JarFile file() { ++ return PaperPluginParent.this.jarFile; ++ } ++ ++ @Override ++ public JavaPlugin createInstance() { ++ PluginBootstrap bootstrap = null; ++ if (this.bootstrapProvider != null && this.bootstrapProvider.getLastProvided() != null) { ++ bootstrap = this.bootstrapProvider.getLastProvided(); ++ } ++ ++ try { ++ JavaPlugin plugin; ++ if (bootstrap == null) { ++ plugin = ProviderUtil.loadClass(PaperPluginParent.this.description.getMainClass(), JavaPlugin.class, PaperPluginParent.this.classLoader); ++ } else { ++ plugin = bootstrap.createPlugin(PaperPluginParent.this.context); ++ } ++ ++ // Don't allow plugins to load plugins other than the one defined in main. This restriction might not be necessary. ++ if (!plugin.getClass().isAssignableFrom(Class.forName(PaperPluginParent.this.description.getMainClass(), true, plugin.getClass().getClassLoader()))) { ++ throw new IllegalArgumentException("Plugin provided must be the same type as main defined in plugin configuration!"); ++ } ++ ++ this.status = ProviderStatus.INITIALIZED; ++ return plugin; ++ } catch (Throwable throwable) { ++ this.status = ProviderStatus.ERRORED; ++ SneakyThrow.sneaky(throwable); ++ } ++ ++ throw new AssertionError(); // Impossible ++ } ++ ++ @Override ++ public PaperPluginMeta getMeta() { ++ return PaperPluginParent.this.description; ++ } ++ ++ @Override ++ public Logger getLogger() { ++ return PaperPluginParent.this.logger; ++ } ++ ++ @Override ++ public ProviderStatus getLastProvidedStatus() { ++ return this.status; ++ } ++ ++ @Override ++ public void setStatus(ProviderStatus status) { ++ this.status = status; ++ } ++ ++ public boolean shouldSkipCreation() { ++ if (this.bootstrapProvider == null) { ++ return false; ++ } ++ ++ return this.bootstrapProvider.getLastProvidedStatus() == ProviderStatus.ERRORED; ++ } ++ ++ /* ++ The plugin has to reuse the classloader in order to share the bootstrapper. ++ However, a plugin may have totally separate dependencies during bootstrapping. ++ This is a bit yuck, but in general we have to treat bootstrapping and normal game as connected. ++ */ ++ @Override ++ public void setContext(DependencyContext context) { ++ PaperPluginParent.this.classLoader.refreshClassloaderDependencyTree(context); ++ } ++ ++ @Override ++ public String toString() { ++ return "PaperServerPluginProvider{" + ++ "parent=" + PaperPluginParent.this + ++ "bootstrapProvider=" + bootstrapProvider + ++ ", status=" + status + ++ '}'; ++ } ++ } ++ ++ ++ @Override ++ public String toString() { ++ return "PaperPluginParent{" + ++ "path=" + path + ++ ", jarFile=" + jarFile + ++ ", description=" + description + ++ ", classLoader=" + classLoader + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type.paper; ++ ++import com.destroystokyo.paper.utils.PaperPluginLogger; ++import io.papermc.paper.plugin.bootstrap.PluginProviderContext; ++import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl; ++import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; ++import io.papermc.paper.plugin.entrypoint.classloader.PaperSimplePluginClassLoader; ++import io.papermc.paper.plugin.loader.PaperClasspathBuilder; ++import io.papermc.paper.plugin.loader.PluginLoader; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import io.papermc.paper.plugin.provider.type.PluginTypeFactory; ++import io.papermc.paper.plugin.provider.util.ProviderUtil; ++ ++import java.io.BufferedReader; ++import java.io.IOException; ++import java.io.InputStreamReader; ++import java.nio.file.Path; ++import java.util.jar.JarEntry; ++import java.util.jar.JarFile; ++import java.util.logging.Logger; ++ ++class PaperPluginProviderFactory implements PluginTypeFactory<PaperPluginParent, PaperPluginMeta> { ++ ++ @Override ++ public PaperPluginParent build(JarFile file, PaperPluginMeta configuration, Path source) throws Exception { ++ Logger logger = PaperPluginLogger.getLogger(configuration); ++ PluginProviderContext context = PluginProviderContextImpl.of(configuration, logger); ++ ++ PaperClasspathBuilder builder = new PaperClasspathBuilder(context); ++ ++ if (configuration.getLoader() != null) { ++ try ( ++ PaperSimplePluginClassLoader simplePluginClassLoader = new PaperSimplePluginClassLoader(source, file, configuration, this.getClass().getClassLoader()) ++ ) { ++ PluginLoader loader = ProviderUtil.loadClass(configuration.getLoader(), PluginLoader.class, simplePluginClassLoader); ++ loader.classloader(builder); ++ } catch (IOException e) { ++ throw new RuntimeException(e); ++ } ++ } ++ ++ PaperPluginClassLoader classLoader = builder.buildClassLoader(logger, source, file, configuration); ++ return new PaperPluginParent(source, file, configuration, classLoader, context); ++ } ++ ++ @Override ++ public PaperPluginMeta create(JarFile file, JarEntry config) throws Exception { ++ PaperPluginMeta configuration; ++ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file.getInputStream(config)))) { ++ configuration = PaperPluginMeta.create(bufferedReader); ++ } ++ return configuration; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type.spigot; ++ ++import com.destroystokyo.paper.util.SneakyThrow; ++import com.destroystokyo.paper.utils.PaperPluginLogger; ++import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.ProviderStatus; ++import io.papermc.paper.plugin.provider.ProviderStatusHolder; ++import io.papermc.paper.plugin.provider.type.PluginTypeFactory; ++import org.bukkit.Bukkit; ++import org.bukkit.Server; ++import org.bukkit.plugin.InvalidPluginException; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.UnknownDependencyException; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.bukkit.plugin.java.LibraryLoader; ++import org.bukkit.plugin.java.PluginClassLoader; ++import org.jetbrains.annotations.NotNull; ++ ++import java.io.File; ++import java.nio.file.Path; ++import java.util.HashSet; ++import java.util.Set; ++import java.util.jar.JarFile; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++public class SpigotPluginProvider implements PluginProvider<JavaPlugin>, ProviderStatusHolder, DependencyContextHolder { ++ ++ public static final PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> FACTORY = new SpigotPluginProviderFactory(); ++ private static final LibraryLoader LIBRARY_LOADER = new LibraryLoader(Logger.getLogger("SpigotLibraryLoader")); ++ private final Path path; ++ private final PluginDescriptionFile description; ++ private final JarFile jarFile; ++ private final Logger logger; ++ private ProviderStatus status; ++ private DependencyContext dependencyContext; ++ ++ SpigotPluginProvider(Path path, JarFile file, PluginDescriptionFile description) { ++ this.path = path; ++ this.jarFile = file; ++ this.description = description; ++ this.logger = PaperPluginLogger.getLogger(description); ++ } ++ ++ @Override ++ public @NotNull Path getSource() { ++ return this.path; ++ } ++ ++ @Override ++ public JarFile file() { ++ return this.jarFile; ++ } ++ ++ @Override ++ public JavaPlugin createInstance() { ++ Server server = Bukkit.getServer(); ++ try { ++ ++ final File parentFile = server.getPluginsFolder(); // Paper ++ final File dataFolder = new File(parentFile, this.description.getName()); ++ @SuppressWarnings("deprecation") final File oldDataFolder = new File(parentFile, this.description.getRawName()); ++ ++ // Found old data folder ++ if (dataFolder.equals(oldDataFolder)) { ++ // They are equal -- nothing needs to be done! ++ } else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) { ++ server.getLogger().warning(String.format( ++ "While loading %s (%s) found old-data folder: `%s' next to the new one `%s'", ++ this.description.getFullName(), ++ this.path, ++ oldDataFolder, ++ dataFolder ++ )); ++ } else if (oldDataFolder.isDirectory() && !dataFolder.exists()) { ++ if (!oldDataFolder.renameTo(dataFolder)) { ++ throw new InvalidPluginException("Unable to rename old data folder: `" + oldDataFolder + "' to: `" + dataFolder + "'"); ++ } ++ server.getLogger().log(Level.INFO, String.format( ++ "While loading %s (%s) renamed data folder: `%s' to `%s'", ++ this.description.getFullName(), ++ this.path, ++ oldDataFolder, ++ dataFolder ++ )); ++ } ++ ++ if (dataFolder.exists() && !dataFolder.isDirectory()) { ++ throw new InvalidPluginException(String.format( ++ "Projected datafolder: `%s' for %s (%s) exists and is not a directory", ++ dataFolder, ++ this.description.getFullName(), ++ this.path ++ )); ++ } ++ ++ Set<String> missingHardDependencies = new HashSet<>(this.description.getDepend().size()); // Paper - list all missing hard depends ++ for (final String pluginName : this.description.getDepend()) { ++ if (!this.dependencyContext.hasDependency(pluginName)) { ++ missingHardDependencies.add(pluginName); // Paper - list all missing hard depends ++ } ++ } ++ // Paper start - list all missing hard depends ++ if (!missingHardDependencies.isEmpty()) { ++ throw new UnknownDependencyException(missingHardDependencies, this.description.getFullName()); ++ } ++ // Paper end ++ ++ server.getUnsafe().checkSupported(this.description); ++ ++ final PluginClassLoader loader; ++ try { ++ loader = new PluginClassLoader(this.getClass().getClassLoader(), this.description, dataFolder, this.path.toFile(), LIBRARY_LOADER.createLoader(this.description), this.dependencyContext); // Paper ++ } catch (InvalidPluginException ex) { ++ throw ex; ++ } catch (Throwable ex) { ++ throw new InvalidPluginException(ex); ++ } ++ ++ // Override dependency context. ++ // We must provide a temporary context in order to properly handle dependencies on the plugin classloader constructor. ++ loader.dependencyContext = PaperPluginManagerImpl.getInstance(); ++ ++ this.status = ProviderStatus.INITIALIZED; ++ return loader.plugin; ++ } catch (Throwable ex) { ++ this.status = ProviderStatus.ERRORED; ++ SneakyThrow.sneaky(ex); ++ } ++ ++ throw new AssertionError(); // Shouldn't happen ++ } ++ ++ @Override ++ public PluginDescriptionFile getMeta() { ++ return this.description; ++ } ++ ++ @Override ++ public Logger getLogger() { ++ return this.logger; ++ } ++ ++ @Override ++ public ProviderStatus getLastProvidedStatus() { ++ return this.status; ++ } ++ ++ @Override ++ public void setStatus(ProviderStatus status) { ++ this.status = status; ++ } ++ ++ @Override ++ public void setContext(DependencyContext context) { ++ this.dependencyContext = context; ++ } ++ ++ @Override ++ public String toString() { ++ return "SpigotPluginProvider{" + ++ "path=" + path + ++ ", description=" + description + ++ ", jarFile=" + jarFile + ++ ", status=" + status + ++ ", dependencyContext=" + dependencyContext + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.provider.type.spigot; ++ ++import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints; ++import io.papermc.paper.plugin.provider.type.PluginTypeFactory; ++import org.bukkit.plugin.InvalidDescriptionException; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.yaml.snakeyaml.error.YAMLException; ++ ++import java.io.IOException; ++import java.io.InputStream; ++import java.nio.file.Path; ++import java.util.Locale; ++import java.util.jar.JarEntry; ++import java.util.jar.JarFile; ++ ++class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> { ++ ++ @Override ++ public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws Exception { ++ // Copied from SimplePluginManager#loadPlugins ++ // Spigot doesn't validate the name when the config is created, and instead when the plugin is loaded. ++ // Paper plugin configuration will do these checks in config serializer instead of when this is created. ++ String name = configuration.getRawName(); ++ if (PluginConfigConstraints.RESERVED_KEYS.contains(name.toLowerCase(Locale.ROOT))) { ++ throw new InvalidDescriptionException("Restricted name, cannot use %s as a plugin name.".formatted(name)); ++ } else if (name.indexOf(' ') != -1) { ++ throw new InvalidDescriptionException("Restricted name, cannot use 0x20 (space character) in a plugin name."); ++ } ++ ++ return new SpigotPluginProvider(source, file, configuration); ++ } ++ ++ @Override ++ public PluginDescriptionFile create(JarFile file, JarEntry config) throws Exception { ++ PluginDescriptionFile descriptionFile; ++ try (InputStream inputStream = file.getInputStream(config)) { ++ descriptionFile = new PluginDescriptionFile(inputStream); ++ } catch (IOException | YAMLException ex) { ++ throw new InvalidDescriptionException(ex); ++ } ++ ++ return descriptionFile; ++ } ++} ++ +diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.storage; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.PluginInitializerManager; ++import io.papermc.paper.plugin.bootstrap.PluginBootstrap; ++import io.papermc.paper.plugin.bootstrap.PluginProviderContext; ++import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder; ++import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.ProviderStatus; ++import io.papermc.paper.plugin.provider.ProviderStatusHolder; ++import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; ++import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration; ++import org.slf4j.Logger; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public class BootstrapProviderStorage extends SimpleProviderStorage<PluginBootstrap> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public BootstrapProviderStorage() { ++ super(new ModernPluginLoadingStrategy<>(new ProviderConfiguration<>() { ++ @Override ++ public void applyContext(PluginProvider<PluginBootstrap> provider, DependencyContext dependencyContext) { ++ if (provider instanceof DependencyContextHolder contextHolder) { ++ contextHolder.setContext(dependencyContext); ++ } ++ } ++ ++ @Override ++ public boolean load(PluginProvider<PluginBootstrap> provider, PluginBootstrap provided) { ++ try { ++ PluginProviderContext context = PluginProviderContextImpl.of(provider, PluginInitializerManager.instance().pluginDirectoryPath()); ++ provided.bootstrap(context); ++ return true; ++ } catch (Exception e) { ++ LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e); ++ if (provider instanceof ProviderStatusHolder statusHolder) { ++ statusHolder.setStatus(ProviderStatus.ERRORED); ++ } ++ return false; ++ } ++ } ++ ++ @Override ++ public List<String> requiredDependencies(PluginProvider<PluginBootstrap> provider) { ++ List<String> dependencies = new ArrayList<>(); ++ if (provider.getMeta() instanceof PaperPluginMeta paperPluginMeta) { ++ for (DependencyConfiguration configuration : paperPluginMeta.getDependencies()) { ++ if (configuration.required() && configuration.bootstrap()) { ++ dependencies.add(configuration.name()); ++ } ++ } ++ ++ return dependencies; ++ } ++ ++ throw new IllegalStateException(); ++ } ++ ++ @Override ++ public List<String> optionalDependencies(PluginProvider<PluginBootstrap> provider) { ++ List<String> dependencies = new ArrayList<>(); ++ if (provider.getMeta() instanceof PaperPluginMeta paperPluginMeta) { ++ for (DependencyConfiguration configuration : paperPluginMeta.getDependencies()) { ++ if (!configuration.required() && configuration.bootstrap()) { ++ dependencies.add(configuration.name()); ++ } ++ } ++ ++ return dependencies; ++ } ++ ++ throw new IllegalStateException(); ++ } ++ ++ @Override ++ public List<String> loadBeforeDependencies(PluginProvider<PluginBootstrap> provider) { ++ return provider.getMeta().getLoadBeforePlugins(); ++ } ++ })); ++ } ++ ++ @Override ++ protected void handleCycle(PluginGraphCycleException exception) { ++ List<String> logMessages = new ArrayList<>(); ++ for (List<String> list : exception.getCycles()) { ++ // CoolPlugin depends on Dependency depends on CoolPlugin... ++ logMessages.add(String.join(" depends on ", list) + " depends on " + list.get(0) + "..."); ++ } ++ ++ LOGGER.error("Circular dependencies detected!"); ++ LOGGER.error("You have a plugin that is depending on a plugin which refers back to that plugin. Your server will shut down until these are resolved, or the strategy is changed."); ++ LOGGER.error("Circular dependencies:"); ++ for (String message : logMessages) { ++ LOGGER.error(message); ++ } ++ LOGGER.error("If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true"); ++ ++ System.exit(-1); ++ } ++ ++ @Override ++ public String toString() { ++ return "BOOTSTRAP:" + super.toString(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.storage; ++ ++import io.papermc.paper.plugin.entrypoint.strategy.LegacyPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; ++ ++import java.util.ArrayList; ++import java.util.List; ++import java.util.logging.Level; ++import java.util.logging.Logger; ++ ++public abstract class ConfiguredProviderStorage<T> extends SimpleProviderStorage<T> { ++ ++ private static final Logger LOGGER = Logger.getLogger("ConfiguredOrderedProviderStorage"); ++ public static final boolean LEGACY_PLUGIN_LOADING = Boolean.getBoolean("paper.useLegacyPluginLoading"); ++ ++ protected ConfiguredProviderStorage(ProviderConfiguration<T> onLoad) { ++ // This doesn't work with reloading. ++ // Should we care? ++ super(LEGACY_PLUGIN_LOADING ? new LegacyPluginLoadingStrategy<>(onLoad) : new ModernPluginLoadingStrategy<>(onLoad)); ++ } ++ ++ @Override ++ protected void handleCycle(PluginGraphCycleException exception) { ++ List<String> logMessages = new ArrayList<>(); ++ for (List<String> list : exception.getCycles()) { ++ // CoolPlugin depends on Dependency depends on CoolPlugin... ++ logMessages.add(String.join(" depends on ", list) + " depends on " + list.get(0) + "..."); ++ } ++ ++ LOGGER.log(Level.SEVERE, "Circular dependencies detected!"); ++ LOGGER.log(Level.SEVERE, "You have a plugin that is depending on a plugin which refers back to that plugin. Your server will shut down until these are resolved, or the strategy is changed."); ++ LOGGER.log(Level.SEVERE, "Circular dependencies:"); ++ for (String message : logMessages) { ++ LOGGER.log(Level.SEVERE, message); ++ } ++ LOGGER.log(Level.SEVERE, "If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true"); ++ ++ if (this.exitOnCycleDependencies()) { ++ System.exit(-1); ++ } ++ } ++ ++ public boolean exitOnCycleDependencies() { ++ return true; ++ } ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.storage; ++ ++import io.papermc.paper.plugin.provider.PluginProvider; ++ ++/** ++ * A provider storage is meant to be a singleton that stores providers. ++ * ++ * @param <T> provider type ++ */ ++public interface ProviderStorage<T> { ++ ++ void register(PluginProvider<T> provider); ++ ++ void enter(); ++ ++ Iterable<PluginProvider<T>> getRegisteredProviders(); ++ ++} +diff --git a/src/main/java/io/papermc/paper/plugin/storage/ServerPluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ServerPluginProviderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/storage/ServerPluginProviderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.storage; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; ++import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import io.papermc.paper.plugin.provider.ProviderStatus; ++import io.papermc.paper.plugin.provider.ProviderStatusHolder; ++import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; ++import org.bukkit.plugin.Plugin; ++import org.bukkit.plugin.java.JavaPlugin; ++import org.slf4j.Logger; ++ ++import java.util.List; ++ ++public class ServerPluginProviderStorage extends ConfiguredProviderStorage<JavaPlugin> { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public ServerPluginProviderStorage() { ++ super(new ProviderConfiguration<>() { ++ @Override ++ public void applyContext(PluginProvider<JavaPlugin> provider, DependencyContext dependencyContext) { ++ Plugin alreadyLoadedPlugin = PaperPluginManagerImpl.getInstance().getPlugin(provider.getMeta().getName()); ++ if (alreadyLoadedPlugin != null) { ++ throw new IllegalStateException("Provider " + provider + " attempted to add duplicate plugin identifier " + alreadyLoadedPlugin + " THIS WILL CREATE BUGS!!!"); ++ } ++ ++ if (provider instanceof DependencyContextHolder contextHolder) { ++ contextHolder.setContext(dependencyContext); ++ } ++ } ++ ++ @Override ++ public boolean load(PluginProvider<JavaPlugin> provider, JavaPlugin provided) { ++ try { ++ provided.getLogger().info(String.format("Loading server plugin %s", provided.getPluginMeta().getDisplayName())); ++ PaperPluginManagerImpl.getInstance().loadPlugin(provided); // We have to add it to the map before the plugin is loaded ++ provided.onLoad(); ++ return true; ++ } catch (Throwable ex) { ++ if (provider instanceof ProviderStatusHolder statusHolder) { ++ statusHolder.setStatus(ProviderStatus.ERRORED); ++ } ++ LOGGER.error("Could not load server plugin '%s' in folder '%s' (Is it up to date?)".formatted(provider.getFileName(), provider.getParentSource()), ex); ++ return false; ++ } ++ } ++ ++ @Override ++ public List<String> requiredDependencies(PluginProvider<JavaPlugin> provider) { ++ return provider.getMeta().getPluginDependencies(); ++ } ++ ++ @Override ++ public List<String> optionalDependencies(PluginProvider<JavaPlugin> provider) { ++ return provider.getMeta().getPluginSoftDependencies(); ++ } ++ ++ @Override ++ public List<String> loadBeforeDependencies(PluginProvider<JavaPlugin> provider) { ++ return provider.getMeta().getLoadBeforePlugins(); ++ } ++ }); ++ } ++ ++ @Override ++ protected void filterLoadingProviders(List<PluginProvider<JavaPlugin>> pluginProviders) { ++ /* ++ Have to do this to prevent loading plugin providers that have failed initializers. ++ This is a hack and a better solution here would be to store failed plugin providers elsewhere. ++ */ ++ pluginProviders.removeIf((provider) -> (provider instanceof PaperPluginParent.PaperServerPluginProvider pluginProvider && pluginProvider.shouldSkipCreation())); ++ } ++ ++ @Override ++ public String toString() { ++ return "PLUGIN:" + super.toString(); ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.storage; ++ ++import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderLoadingStrategy; ++import io.papermc.paper.plugin.provider.PluginProvider; ++ ++import java.util.ArrayList; ++import java.util.List; ++ ++public abstract class SimpleProviderStorage<T> implements ProviderStorage<T> { ++ ++ protected final List<PluginProvider<T>> providers = new ArrayList<>(); ++ protected ProviderLoadingStrategy<T> strategy; ++ ++ protected SimpleProviderStorage(ProviderLoadingStrategy<T> strategy) { ++ this.strategy = strategy; ++ } ++ ++ @Override ++ public void register(PluginProvider<T> provider) { ++ this.providers.add(provider); ++ } ++ ++ @Override ++ public void enter() { ++ List<PluginProvider<T>> providerList = new ArrayList<>(this.providers); ++ this.filterLoadingProviders(providerList); ++ ++ try { ++ for (T plugin : this.strategy.loadProviders(providerList)) { ++ this.processProvided(plugin); ++ } ++ } catch (PluginGraphCycleException exception) { ++ this.handleCycle(exception); ++ } ++ } ++ ++ @Override ++ public Iterable<PluginProvider<T>> getRegisteredProviders() { ++ return this.providers; ++ } ++ ++ public void processProvided(T provided) {} ++ ++ // Mutable enter ++ protected void filterLoadingProviders(List<PluginProvider<T>> providers) {} ++ ++ protected abstract void handleCycle(PluginGraphCycleException exception); ++ ++ @Override ++ public String toString() { ++ return "SimpleProviderStorage{" + ++ "providers=" + this.providers + ++ ", strategy=" + this.strategy + ++ '}'; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/storage/package-info.java b/src/main/java/io/papermc/paper/plugin/storage/package-info.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/storage/package-info.java +@@ -0,0 +0,0 @@ ++/** ++ * Classes in this package are supposed to connect components of {@link io.papermc.paper.plugin.entrypoint} and {@link io.papermc.paper.plugin.provider} packages. ++ * @see io.papermc.paper.plugin.entrypoint.Entrypoint ++ */ ++package io.papermc.paper.plugin.storage; +diff --git a/src/main/java/io/papermc/paper/plugin/util/EntrypointUtil.java b/src/main/java/io/papermc/paper/plugin/util/EntrypointUtil.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/util/EntrypointUtil.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.util; ++ ++import com.mojang.logging.LogUtils; ++import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler; ++import io.papermc.paper.plugin.provider.source.ProviderSource; ++import org.slf4j.Logger; ++ ++public class EntrypointUtil { ++ ++ private static final Logger LOGGER = LogUtils.getLogger(); ++ ++ public static <C> void registerProvidersFromSource(ProviderSource<C> source, C context) { ++ try { ++ source.registerProviders(LaunchEntryPointHandler.INSTANCE, context); ++ } catch (Throwable e) { ++ LOGGER.error(e.getMessage(), e); ++ } ++ } ++} +diff --git a/src/main/java/io/papermc/paper/plugin/util/NamespaceChecker.java b/src/main/java/io/papermc/paper/plugin/util/NamespaceChecker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/plugin/util/NamespaceChecker.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin.util; ++ ++import org.jetbrains.annotations.ApiStatus; ++import org.jetbrains.annotations.NotNull; ++ ++@ApiStatus.Internal ++public class NamespaceChecker { ++ ++ private static final String[] QUICK_INVALID_NAMESPACES = { ++ "net.minecraft.", ++ "org.bukkit.", ++ "io.papermc.paper.", ++ "com.destroystokoyo.paper." ++ }; ++ ++ /** ++ * Used for a variety of namespaces that shouldn't be resolved and should instead be moved to ++ * other classloaders. We can assume this because only plugins should be using this classloader. ++ * ++ * @param name namespace ++ */ ++ public static void validateNameSpaceForClassloading(@NotNull String name) throws ClassNotFoundException { ++ if (!isValidNameSpace(name)) { ++ throw new ClassNotFoundException(name); ++ } ++ } ++ ++ public static boolean isValidNameSpace(@NotNull String name) { ++ for (String string : QUICK_INVALID_NAMESPACES) { ++ if (name.startsWith(string)) { ++ return false; ++ } ++ } ++ ++ return true; ++ } ++} +diff --git a/src/main/java/io/papermc/paper/util/StackWalkerUtil.java b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/io/papermc/paper/util/StackWalkerUtil.java ++++ b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java +@@ -0,0 +0,0 @@ + package io.papermc.paper.util; + ++import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; + import org.bukkit.plugin.java.JavaPlugin; + import org.bukkit.plugin.java.PluginClassLoader; + import org.jetbrains.annotations.Nullable; + ++import java.util.Objects; + import java.util.Optional; + + public class StackWalkerUtil { +@@ -0,0 +0,0 @@ public class StackWalkerUtil { + public static JavaPlugin getFirstPluginCaller() { + Optional<JavaPlugin> foundFrame = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE) + .walk(stream -> stream +- .filter(frame -> frame.getDeclaringClass().getClassLoader() instanceof PluginClassLoader) + .map((frame) -> { +- PluginClassLoader classLoader = (PluginClassLoader) frame.getDeclaringClass().getClassLoader(); +- return classLoader.getPlugin(); ++ ClassLoader classLoader = frame.getDeclaringClass().getClassLoader(); ++ JavaPlugin plugin; ++ if (classLoader instanceof PaperPluginClassLoader pluginClassLoader) { ++ plugin = pluginClassLoader.getLoadedJavaPlugin(); ++ } else if (classLoader instanceof PluginClassLoader spigotClassloader) { ++ plugin = spigotClassloader.getPlugin(); ++ } else { ++ plugin = null; ++ } ++ ++ return plugin; + }) ++ .filter(Objects::nonNull) + .findFirst()); + + return foundFrame.orElse(null); +diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java ++++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java +@@ -0,0 +0,0 @@ public class BuiltInRegistries { + } + + public static void bootStrap() { ++ // Paper start ++ bootStrap(() -> {}); ++ } ++ public static void bootStrap(Runnable runnable) { ++ // Paper end + createContents(); ++ runnable.run(); // Paper + freeze(); + validate(REGISTRY); + } +diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/net/minecraft/server/Bootstrap.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/Bootstrap.java ++++ b/src/main/java/net/minecraft/server/Bootstrap.java +@@ -0,0 +0,0 @@ public class Bootstrap { + EntitySelectorOptions.bootStrap(); + DispenseItemBehavior.bootStrap(); + CauldronInteraction.bootStrap(); +- BuiltInRegistries.bootStrap(); ++ // Paper start ++ BuiltInRegistries.bootStrap(() -> { ++ io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping ++ }); ++ // Paper end + Bootstrap.wrapStreams(); + } + // CraftBukkit start - easier than fixing the decompile +diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/Main.java ++++ b/src/main/java/net/minecraft/server/Main.java +@@ -0,0 +0,0 @@ public class Main { + JvmProfiler.INSTANCE.start(Environment.SERVER); + } + ++ // Paper start ++ ++ // We have to load the bukkit configuration inorder to get the update folder location. ++ io.papermc.paper.plugin.PluginInitializerManager pluginSystem = io.papermc.paper.plugin.PluginInitializerManager.init(optionset); ++ // Register the default plugin directory ++ io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.DirectoryProviderSource.INSTANCE, pluginSystem.pluginDirectoryPath()); ++ @SuppressWarnings("unchecked") ++ java.util.List<File> files = (java.util.List<File>) optionset.valuesOf("add-plugin"); ++ // Register plugins from the flag ++ io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.PluginFlagProviderSource.INSTANCE, files); ++ // Paper end + Bootstrap.bootStrap(); + Bootstrap.validate(); + Util.startTimerHackThread(); +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -0,0 +0,0 @@ public final class CraftServer implements Server { + private final CraftCommandMap commandMap = new CraftCommandMap(this); + private final SimpleHelpMap helpMap = new SimpleHelpMap(this); + private final StandardMessenger messenger = new StandardMessenger(); +- private final SimplePluginManager pluginManager = new SimplePluginManager(this, this.commandMap); ++ private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap); ++ public final io.papermc.paper.plugin.manager.PaperPluginManagerImpl paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); {this.pluginManager.paperPluginManager = this.paperPluginManager;} // Paper + private final StructureManager structureManager; + protected final DedicatedServer console; + protected final DedicatedPlayerList playerList; +@@ -0,0 +0,0 @@ public final class CraftServer implements Server { + } + + public void loadPlugins() { +- this.pluginManager.registerInterface(JavaPluginLoader.class); +- +- File pluginFolder = (File) console.options.valueOf("plugins"); +- +- if (pluginFolder.exists()) { +- Plugin[] plugins = this.pluginManager.loadPlugins(pluginFolder); +- for (Plugin plugin : plugins) { +- try { +- String message = String.format("Loading %s", plugin.getDescription().getFullName()); +- plugin.getLogger().info(message); +- plugin.onLoad(); +- } catch (Throwable ex) { +- Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, ex.getMessage() + " initializing " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex); +- } +- } +- } else { +- pluginFolder.mkdir(); +- } ++ io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.INSTANCE.enter(io.papermc.paper.plugin.entrypoint.Entrypoint.PLUGIN); // Paper - replace implementation + } + + public void enablePlugins(PluginLoadOrder type) { +@@ -0,0 +0,0 @@ public final class CraftServer implements Server { + private void enablePlugin(Plugin plugin) { + try { + List<Permission> perms = plugin.getDescription().getPermissions(); +- ++ List<Permission> permsToLoad = new ArrayList<>(); // Paper + for (Permission perm : perms) { +- try { +- this.pluginManager.addPermission(perm, false); +- } catch (IllegalArgumentException ex) { +- this.getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex); ++ // Paper start ++ if (this.paperPluginManager.getPermission(perm.getName()) == null) { ++ permsToLoad.add(perm); ++ } else { ++ this.getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered"); ++ // Paper end + } + } +- this.pluginManager.dirtyPermissibles(); ++ this.paperPluginManager.addPermissions(permsToLoad); // Paper + + this.pluginManager.enablePlugin(plugin); + } catch (Throwable ex) { +diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java ++++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java +@@ -0,0 +0,0 @@ public class MinecraftInternalPlugin extends PluginBase { + public PluginDescriptionFile getDescription() { + return pdf; + } ++ // Paper start ++ @Override ++ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() { ++ return pdf; ++ } ++ // Paper end + + @Override + public FileConfiguration getConfig() { +diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java ++++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +@@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues { + net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack); + return nmsItemStack.getItem().getDescriptionId(nmsItemStack); + } ++ // Paper start ++ @Override ++ public boolean isSupportedApiVersion(String apiVersion) { ++ return apiVersion != null && SUPPORTED_API.contains(apiVersion); ++ } ++ // Paper end + + /** + * This helper class represents the different NBT Tags. +diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier b/src/main/resources/META-INF/services/io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier +@@ -0,0 +1 @@ ++io.papermc.paper.plugin.entrypoint.classloader.PaperClassloaderBytecodeModifier +diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage b/src/main/resources/META-INF/services/io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage +@@ -0,0 +1 @@ ++io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage +diff --git a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.bukkit.Server; ++import org.bukkit.command.Command; ++import org.bukkit.command.CommandSender; ++import org.bukkit.configuration.file.FileConfiguration; ++import org.bukkit.generator.BiomeProvider; ++import org.bukkit.generator.ChunkGenerator; ++import org.bukkit.plugin.PluginBase; ++import org.bukkit.plugin.PluginDescriptionFile; ++import org.bukkit.plugin.PluginLoader; ++import org.bukkit.plugin.PluginLogger; ++ ++import java.io.File; ++import java.io.InputStream; ++import java.util.List; ++ ++public class PaperTestPlugin extends PluginBase { ++ private final String pluginName; ++ private boolean enabled = true; ++ private final PluginMeta configuration; ++ ++ public PaperTestPlugin(String pluginName) { ++ this.pluginName = pluginName; ++ this.configuration = new TestPluginMeta(pluginName); ++ } ++ ++ public PaperTestPlugin(PluginMeta configuration) { ++ this.configuration = configuration; ++ this.pluginName = configuration.getName(); ++ } ++ ++ @Override ++ public File getDataFolder() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginDescriptionFile getDescription() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginMeta getPluginMeta() { ++ return this.configuration; ++ } ++ ++ @Override ++ public FileConfiguration getConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public InputStream getResource(String filename) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveDefaultConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void saveResource(String resourcePath, boolean replace) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void reloadConfig() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginLogger getLogger() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public PluginLoader getPluginLoader() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public Server getServer() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean isEnabled() { ++ return enabled; ++ } ++ ++ public void setEnabled(boolean enabled) { ++ this.enabled = enabled; ++ } ++ ++ @Override ++ public void onDisable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void onLoad() { ++ } ++ ++ @Override ++ public void onEnable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean isNaggable() { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public void setNaggable(boolean canNag) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public BiomeProvider getDefaultBiomeProvider(String worldName, String id) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++ ++ @Override ++ public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) { ++ throw new UnsupportedOperationException("Not supported."); ++ } ++} +diff --git a/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java b/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; ++import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy; ++import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; ++import io.papermc.paper.plugin.provider.PluginProvider; ++import org.junit.Assert; ++import org.junit.Before; ++import org.junit.Test; ++ ++import java.util.ArrayList; ++import java.util.HashMap; ++import java.util.List; ++import java.util.Map; ++import java.util.concurrent.atomic.AtomicInteger; ++ ++public class PluginDependencyLoadingTest { ++ ++ private static List<PluginProvider<PaperTestPlugin>> REGISTERED_PROVIDERS = new ArrayList<>(); ++ private static Map<String, Integer> LOAD_ORDER = new HashMap<>(); ++ ++ static { ++ setup(); ++ } ++ ++ private static TestJavaPluginProvider setup(String identifier, String[] hard, String[] soft, String[] before) { ++ TestPluginMeta configuration = new TestPluginMeta(identifier); ++ configuration.setHardDependencies(List.of(hard)); ++ configuration.setSoftDependencies(List.of(soft)); ++ configuration.setLoadBefore(List.of(before)); ++ ++ TestJavaPluginProvider provider = new TestJavaPluginProvider(configuration); ++ REGISTERED_PROVIDERS.add(provider); ++ return provider; ++ } ++ ++ /** ++ * Obfuscated plugin names, this uses a real dependency tree... ++ */ ++ private static void setup() { ++ setup("RedAir", new String[]{}, new String[]{"NightShovel", "EmeraldFire"}, new String[]{"GreenShovel", "IronSpork", "BrightBlueShovel", "WireDoor"}); ++ setup("BigGrass", new String[]{}, new String[]{"IronEarth", "RedAir"}, new String[]{"BlueFire"}); ++ setup("BlueFire", new String[]{}, new String[]{}, new String[]{}); ++ setup("BigPaper", new String[]{}, new String[]{"BlueFire"}, new String[]{}); ++ setup("EmeraldSpork", new String[]{}, new String[]{}, new String[]{"GoldPaper", "YellowSnow"}); ++ setup("GreenShovel", new String[]{}, new String[]{}, new String[]{}); ++ setup("BrightBlueGrass", new String[]{"BigPaper"}, new String[]{"DarkSpork"}, new String[]{}); ++ setup("GoldPaper", new String[]{}, new String[]{"BlueFire"}, new String[]{}); ++ setup("GreenGlass", new String[]{}, new String[]{}, new String[]{}); ++ setup("GoldNeptune", new String[]{}, new String[]{"GreenShovel", "GoldNeptuneVersioning"}, new String[]{}); ++ setup("RedPaper", new String[]{}, new String[]{"GoldPaper", "GoldFire", "EmeraldGrass", "BlueFire", "CopperSpork", "YellowDoor", "OrangeClam", "BlueSponge", "GoldNeptune", "BrightBlueGrass", "DarkSpoon", "BigShovel", "GreenGlass", "IronGlass"}, new String[]{"IronPaper", "YellowFire"}); ++ setup("YellowGrass", new String[]{}, new String[]{"RedAir"}, new String[]{}); ++ setup("WireFire", new String[]{}, new String[]{"RedPaper", "WireGrass", "YellowSpork", "NightAir"}, new String[]{}); ++ setup("OrangeNeptune", new String[]{}, new String[]{}, new String[]{}); ++ setup("BigSpoon", new String[]{"YellowGrass", "GreenShovel"}, new String[]{"RedAir", "GoldNeptune", "BrightBlueGrass", "LightDoor", "LightSpork", "LightEarth", "NightDoor", "OrangeSpoon", "GoldSponge", "GoldDoor", "DarkPaper", "RedPaper", "GreenGlass", "IronGlass", "NightGlass", "BigGrass", "BlueFire", "YellowSpoon", "DiamondGrass", "DiamondShovel", "DarkSnow", "EmeraldGlass", "EmeraldSpoon", "LightFire", "WireGrass", "RedEarth", "WireFire"}, new String[]{}); ++ setup("CopperSnow", new String[]{}, new String[]{"RedSnow", "OrangeFire", "WireAir", "GreenGlass", "NightSpork", "EmeraldPaper"}, new String[]{"BlueGrass"}); ++ setup("BrightBluePaper", new String[]{}, new String[]{"GoldEarth", "BrightBlueSpoon", "CopperGlass", "LightSporkChat", "DarkAir", "LightEarth", "DiamondDoor", "YellowShovel", "BlueAir", "DarkShovel", "GoldPaper", "BlueFire", "GreenGlass", "YellowSpork", "BigGrass", "OrangePaper", "DarkPaper"}, new String[]{"WireShovel"}); ++ setup("LightSponge", new String[]{}, new String[]{}, new String[]{}); ++ setup("OrangeShovel", new String[]{}, new String[]{}, new String[]{}); ++ setup("GoldGrass", new String[]{}, new String[]{"GreenGlass", "BlueFire"}, new String[]{}); ++ setup("IronSponge", new String[]{}, new String[]{"DiamondEarth"}, new String[]{}); ++ setup("EmeraldSnow", new String[]{}, new String[]{}, new String[]{}); ++ setup("BlueSpoon", new String[]{"BigGrass"}, new String[]{"GreenGlass", "GoldPaper", "GreenShovel", "YellowClam"}, new String[]{}); ++ setup("BigSpork", new String[]{}, new String[]{"BigPaper"}, new String[]{}); ++ setup("BluePaper", new String[]{}, new String[]{"BigClam", "RedSpoon", "GreenFire", "WireSnow", "OrangeSnow", "BlueFire", "BrightBlueGrass", "YellowSpork", "GreenGlass"}, new String[]{}); ++ setup("OrangeSpork", new String[]{}, new String[]{}, new String[]{}); ++ setup("DiamondNeptune", new String[]{}, new String[]{"GreenGlass", "GreenShovel", "YellowNeptune"}, new String[]{}); ++ setup("BigFire", new String[]{}, new String[]{"BlueFire", "BrightBlueDoor", "GreenGlass"}, new String[]{}); ++ setup("NightNeptune", new String[]{}, new String[]{"BlueFire", "DarkGlass", "GoldPaper", "YellowNeptune", "BlueShovel"}, new String[]{}); ++ setup("YellowEarth", new String[]{"RedAir"}, new String[]{}, new String[]{}); ++ setup("DiamondClam", new String[]{}, new String[]{}, new String[]{}); ++ setup("CopperAir", new String[]{}, new String[]{"BigPaper"}, new String[]{}); ++ setup("NightSpoon", new String[]{"OrangeNeptune"}, new String[]{"BlueFire", "GreenGlass", "RedSpork", "GoldPaper", "BigShovel", "YellowSponge", "EmeraldSpork"}, new String[]{}); ++ setup("GreenClam", new String[]{}, new String[]{"GreenShovel", "BrightBlueEarth", "BigSpoon", "RedPaper", "BlueFire", "GreenGlass", "WireFire", "GreenSnow"}, new String[]{}); ++ setup("YellowPaper", new String[]{}, new String[]{}, new String[]{}); ++ setup("WireGlass", new String[]{"YellowGrass"}, new String[]{"YellowGlass", "BigSpoon", "CopperSnow", "GreenGlass", "BlueEarth"}, new String[]{}); ++ setup("BlueSpork", new String[]{}, new String[]{"BrightBlueGrass"}, new String[]{}); ++ setup("CopperShovel", new String[]{}, new String[]{"GreenGlass"}, new String[]{}); ++ setup("RedClam", new String[]{}, new String[]{}, new String[]{}); ++ setup("EmeraldClam", new String[]{}, new String[]{"BlueFire"}, new String[]{}); ++ setup("DarkClam", new String[]{}, new String[]{"GoldAir", "LightGlass"}, new String[]{}); ++ setup("WireSpoon", new String[]{}, new String[]{"GoldPaper", "LightSnow"}, new String[]{}); ++ setup("CopperNeptune", new String[]{}, new String[]{"GreenGlass", "BigGrass"}, new String[]{}); ++ setup("RedNeptune", new String[]{}, new String[]{}, new String[]{}); ++ setup("GreenAir", new String[]{}, new String[]{}, new String[]{}); ++ setup("RedFire", new String[]{"BrightBlueGrass", "BigPaper"}, new String[]{"BlueFire", "GreenGlass", "BigGrass"}, new String[]{}); ++ } ++ ++ @Before ++ public void loadProviders() { ++ AtomicInteger currentLoad = new AtomicInteger(); ++ ModernPluginLoadingStrategy<PaperTestPlugin> modernPluginLoadingStrategy = new ModernPluginLoadingStrategy<>(new ProviderConfiguration<>() { ++ @Override ++ public void applyContext(PluginProvider<PaperTestPlugin> provider, DependencyContext dependencyContext) { ++ } ++ ++ @Override ++ public boolean load(PluginProvider<PaperTestPlugin> provider, PaperTestPlugin provided) { ++ LOAD_ORDER.put(provider.getMeta().getName(), currentLoad.getAndIncrement()); ++ return false; ++ } ++ ++ @Override ++ public List<String> requiredDependencies(PluginProvider<PaperTestPlugin> provider) { ++ return provider.getMeta().getPluginDependencies(); ++ } ++ ++ @Override ++ public List<String> optionalDependencies(PluginProvider<PaperTestPlugin> provider) { ++ return provider.getMeta().getPluginSoftDependencies(); ++ } ++ ++ @Override ++ public List<String> loadBeforeDependencies(PluginProvider<PaperTestPlugin> provider) { ++ return provider.getMeta().getLoadBeforePlugins(); ++ } ++ }); ++ ++ modernPluginLoadingStrategy.loadProviders(REGISTERED_PROVIDERS); ++ } ++ ++ @Test ++ public void testDependencies() { ++ for (PluginProvider<PaperTestPlugin> provider : REGISTERED_PROVIDERS) { ++ TestPluginMeta pluginMeta = (TestPluginMeta) provider.getMeta(); ++ String identifier = pluginMeta.getName(); ++ Assert.assertTrue("Provider wasn't loaded! (%s)".formatted(identifier), LOAD_ORDER.containsKey(identifier)); ++ ++ int index = LOAD_ORDER.get(identifier); ++ ++ // Hard dependencies should be loaded BEFORE ++ for (String hardDependency : pluginMeta.getPluginDependencies()) { ++ Assert.assertTrue("Plugin (%s) is missing hard dependency (%s)".formatted(identifier, hardDependency), LOAD_ORDER.containsKey(hardDependency)); ++ ++ int dependencyIndex = LOAD_ORDER.get(hardDependency); ++ Assert.assertTrue("Plugin (%s) was not loaded BEFORE soft dependency. (%s)".formatted(identifier, hardDependency), index > dependencyIndex); ++ } ++ ++ for (String softDependency : pluginMeta.getPluginSoftDependencies()) { ++ if (!LOAD_ORDER.containsKey(softDependency)) { ++ continue; ++ } ++ ++ int dependencyIndex = LOAD_ORDER.get(softDependency); ++ ++ Assert.assertTrue("Plugin (%s) was not loaded BEFORE soft dependency. (%s)".formatted(identifier, softDependency), index > dependencyIndex); ++ } ++ ++ for (String loadBefore : pluginMeta.getLoadBeforePlugins()) { ++ if (!LOAD_ORDER.containsKey(loadBefore)) { ++ continue; ++ } ++ ++ int dependencyIndex = LOAD_ORDER.get(loadBefore); ++ Assert.assertTrue("Plugin (%s) was NOT loaded BEFORE loadbefore dependency. (%s)".formatted(identifier, loadBefore), index < dependencyIndex); ++ } ++ } ++ } ++} +diff --git a/src/test/java/io/papermc/paper/plugin/PluginLoadingTest.java b/src/test/java/io/papermc/paper/plugin/PluginLoadingTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +diff --git a/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java b/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import org.bukkit.Bukkit; ++import org.bukkit.event.Event; ++import org.bukkit.permissions.Permission; ++import org.bukkit.plugin.PluginManager; ++import org.bukkit.support.AbstractTestingBase; ++import org.junit.After; ++import org.junit.Test; ++ ++import static org.hamcrest.MatcherAssert.assertThat; ++import static org.hamcrest.Matchers.*; ++ ++public class PluginManagerTest extends AbstractTestingBase { ++ ++ private static final PluginManager pm = Bukkit.getPluginManager(); ++ ++ @Test ++ public void testSyncSameThread() { ++ final Event event = new TestEvent(false); ++ pm.callEvent(event); ++ } ++ ++ @Test ++ public void testRemovePermissionByNameLower() { ++ this.testRemovePermissionByName("lower"); ++ } ++ ++ @Test ++ public void testRemovePermissionByNameUpper() { ++ this.testRemovePermissionByName("UPPER"); ++ } ++ ++ @Test ++ public void testRemovePermissionByNameCamel() { ++ this.testRemovePermissionByName("CaMeL"); ++ } ++ ++ @Test ++ public void testRemovePermissionByPermissionLower() { ++ this.testRemovePermissionByPermission("lower"); ++ } ++ ++ @Test ++ public void testRemovePermissionByPermissionUpper() { ++ this.testRemovePermissionByPermission("UPPER"); ++ } ++ ++ @Test ++ public void testRemovePermissionByPermissionCamel() { ++ this.testRemovePermissionByPermission("CaMeL"); ++ } ++ ++ private void testRemovePermissionByName(final String name) { ++ final Permission perm = new Permission(name); ++ pm.addPermission(perm); ++ assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm)); ++ pm.removePermission(name); ++ assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue())); ++ } ++ ++ private void testRemovePermissionByPermission(final String name) { ++ final Permission perm = new Permission(name); ++ pm.addPermission(perm); ++ assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm)); ++ pm.removePermission(perm); ++ assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue())); ++ } ++ ++ @After ++ public void tearDown() { ++ pm.clearPlugins(); ++ assertThat(pm.getPermissions(), is(empty())); ++ } ++} +diff --git a/src/test/java/io/papermc/paper/plugin/SyntheticEventTest.java b/src/test/java/io/papermc/paper/plugin/SyntheticEventTest.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/SyntheticEventTest.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import io.papermc.paper.plugin.manager.PaperPluginManagerImpl; ++import org.bukkit.Bukkit; ++import org.bukkit.event.Event; ++import org.bukkit.event.EventHandler; ++import org.bukkit.event.Listener; ++import org.junit.Assert; ++import org.junit.Test; ++ ++public class SyntheticEventTest { ++ ++ @Test ++ public void test() { ++ PaperTestPlugin paperTestPlugin = new PaperTestPlugin("synthetictest"); ++ PaperPluginManagerImpl paperPluginManager = new PaperPluginManagerImpl(Bukkit.getServer(), null, null); ++ ++ TestEvent event = new TestEvent(false); ++ Impl impl = new Impl(); ++ ++ paperPluginManager.registerEvents(impl, paperTestPlugin); ++ paperPluginManager.callEvent(event); ++ ++ Assert.assertEquals(1, impl.callCount); ++ } ++ ++ public abstract static class Base<E extends Event> implements Listener { ++ int callCount = 0; ++ ++ public void accept(E evt) { ++ callCount++; ++ } ++ } ++ ++ public static class Impl extends Base<TestEvent> { ++ @Override ++ @EventHandler ++ public void accept(TestEvent evt) { ++ super.accept(evt); ++ } ++ } ++} +diff --git a/src/test/java/io/papermc/paper/plugin/TestEvent.java b/src/test/java/io/papermc/paper/plugin/TestEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/TestEvent.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++ ++import org.bukkit.event.Event; ++import org.bukkit.event.HandlerList; ++ ++public class TestEvent extends Event { ++ private static final HandlerList handlers = new HandlerList(); ++ ++ public TestEvent(boolean async) { ++ super(async); ++ } ++ ++ @Override ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} +diff --git a/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java b/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import io.papermc.paper.plugin.provider.PluginProvider; ++import org.jetbrains.annotations.NotNull; ++ ++import java.nio.file.Path; ++import java.util.jar.JarFile; ++import java.util.logging.Logger; ++ ++public class TestJavaPluginProvider implements PluginProvider<PaperTestPlugin> { ++ ++ private final TestPluginMeta testPluginConfiguration; ++ ++ public TestJavaPluginProvider(TestPluginMeta testPluginConfiguration) { ++ this.testPluginConfiguration = testPluginConfiguration; ++ } ++ ++ @Override ++ public @NotNull Path getSource() { ++ return Path.of("dummy"); ++ } ++ ++ @Override ++ public JarFile file() { ++ throw new UnsupportedOperationException(); ++ } ++ ++ @Override ++ public PaperTestPlugin createInstance() { ++ return new PaperTestPlugin(this.testPluginConfiguration); ++ } ++ ++ @Override ++ public TestPluginMeta getMeta() { ++ return this.testPluginConfiguration; ++ } ++ ++ @Override ++ public Logger getLogger() { ++ return Logger.getLogger("TestPlugin"); ++ } ++} +diff --git a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.plugin; ++ ++import io.papermc.paper.plugin.configuration.PluginMeta; ++import org.bukkit.permissions.Permission; ++import org.bukkit.permissions.PermissionDefault; ++import org.bukkit.plugin.PluginLoadOrder; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++import java.util.List; ++ ++public class TestPluginMeta implements PluginMeta { ++ ++ private final String identifier; ++ private List<String> hardDependencies = List.of(); ++ private List<String> softDependencies = List.of(); ++ private List<String> loadBefore = List.of(); ++ ++ public TestPluginMeta(String identifier) { ++ this.identifier = identifier; ++ } ++ ++ @Override ++ public @NotNull String getName() { ++ return this.identifier; ++ } ++ ++ @Override ++ public @NotNull String getMainClass() { ++ return "null"; ++ } ++ ++ @Override ++ public @NotNull PluginLoadOrder getLoadOrder() { ++ return PluginLoadOrder.POSTWORLD; ++ } ++ ++ @Override ++ public @NotNull String getVersion() { ++ return "1.0"; ++ } ++ ++ @Override ++ public @Nullable String getLoggerPrefix() { ++ return this.identifier; ++ } ++ ++ public void setHardDependencies(List<String> hardDependencies) { ++ this.hardDependencies = hardDependencies; ++ } ++ ++ @Override ++ public @NotNull List<String> getPluginDependencies() { ++ return this.hardDependencies; ++ } ++ ++ public void setSoftDependencies(List<String> softDependencies) { ++ this.softDependencies = softDependencies; ++ } ++ ++ @Override ++ public @NotNull List<String> getPluginSoftDependencies() { ++ return this.softDependencies; ++ } ++ ++ public void setLoadBefore(List<String> loadBefore) { ++ this.loadBefore = loadBefore; ++ } ++ ++ @Override ++ public @NotNull List<String> getLoadBeforePlugins() { ++ return this.loadBefore; ++ } ++ ++ @Override ++ public @NotNull List<String> getProvidedPlugins() { ++ return List.of(); ++ } ++ ++ @Override ++ public @NotNull List<String> getAuthors() { ++ return List.of(); ++ } ++ ++ @Override ++ public @NotNull List<String> getContributors() { ++ return List.of(); ++ } ++ ++ @Override ++ public @Nullable String getDescription() { ++ return "null"; ++ } ++ ++ @Override ++ public @Nullable String getWebsite() { ++ return "null"; ++ } ++ ++ @Override ++ public @NotNull List<Permission> getPermissions() { ++ return List.of(); ++ } ++ ++ @Override ++ public @NotNull PermissionDefault getPermissionDefault() { ++ return PermissionDefault.TRUE; ++ } ++ ++ @Override ++ public @NotNull String getAPIVersion() { ++ return "null"; ++ } ++} +diff --git a/src/test/java/io/papermc/paper/testing/DummyServer.java b/src/test/java/io/papermc/paper/testing/DummyServer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/test/java/io/papermc/paper/testing/DummyServer.java ++++ b/src/test/java/io/papermc/paper/testing/DummyServer.java +@@ -0,0 +0,0 @@ public final class DummyServer { + return new LazyRegistry(() -> CraftRegistry.createRegistry(invocation.getArgument(0, Class.class), AbstractTestingBase.REGISTRY_CUSTOM)); + }); + +- final PluginManager pluginManager = new SimplePluginManager(dummyServer, new SimpleCommandMap(dummyServer)); ++ final PluginManager pluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(dummyServer, new SimpleCommandMap(dummyServer), null); + when(dummyServer.getPluginManager()).thenReturn(pluginManager); + + Bukkit.setServer(dummyServer); diff --git a/patches/server/Rewrite-chunk-system.patch b/patches/server/Rewrite-chunk-system.patch index be0d647ef3..1923c0e788 100644 --- a/patches/server/Rewrite-chunk-system.patch +++ b/patches/server/Rewrite-chunk-system.patch @@ -11566,8 +11566,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 import io.papermc.paper.command.subcommands.FixLightCommand; import io.papermc.paper.command.subcommands.HeapDumpCommand; @@ -0,0 +0,0 @@ public final class PaperCommand extends Command { - commands.put(Set.of("reload"), new ReloadCommand()); commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); commands.put(Set.of("fixlight"), new FixLightCommand()); + commands.put(Set.of("debug", "chunkinfo", "holderinfo"), new ChunkDebugCommand()); diff --git a/patches/server/Starlight.patch b/patches/server/Starlight.patch index 41e8c1ff94..5d8c63684b 100644 --- a/patches/server/Starlight.patch +++ b/patches/server/Starlight.patch @@ -4343,9 +4343,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 import io.papermc.paper.command.subcommands.ReloadCommand; import io.papermc.paper.command.subcommands.VersionCommand; @@ -0,0 +0,0 @@ public final class PaperCommand extends Command { - commands.put(Set.of("entity"), new EntityCommand()); commands.put(Set.of("reload"), new ReloadCommand()); commands.put(Set.of("version"), new VersionCommand()); + commands.put(Set.of("dumpplugins"), new DumpPluginsCommand()); + commands.put(Set.of("fixlight"), new FixLightCommand()); return commands.entrySet().stream() diff --git a/patches/server/Timings-v2.patch b/patches/server/Timings-v2.patch index f1aa454792..5193963dc0 100644 --- a/patches/server/Timings-v2.patch +++ b/patches/server/Timings-v2.patch @@ -2038,8 +2038,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 public static byte toLegacyData(BlockState data) { return CraftLegacy.toLegacyData(data); @@ -0,0 +0,0 @@ public final class CraftMagicNumbers implements UnsafeValues { - return nmsItemStack.getItem().getDescriptionId(nmsItemStack); } + // Paper end + // Paper start + @Override diff --git a/test-plugin/build.gradle.kts b/test-plugin/build.gradle.kts index 6bcdb356c5..e86b933408 100644 --- a/test-plugin/build.gradle.kts +++ b/test-plugin/build.gradle.kts @@ -13,7 +13,7 @@ tasks.processResources { "apiversion" to apiVersion, ) inputs.properties(props) - filesMatching("plugin.yml") { + filesMatching("paper-plugin.yml") { expand(props) } } diff --git a/test-plugin/src/main/java/io/papermc/paper/testplugin/TestPlugin.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java similarity index 88% rename from test-plugin/src/main/java/io/papermc/paper/testplugin/TestPlugin.java rename to test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java index a0c78b9d68..4e68423bb7 100644 --- a/test-plugin/src/main/java/io/papermc/paper/testplugin/TestPlugin.java +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPlugin.java @@ -1,9 +1,10 @@ -package io.papermc.paper.testplugin; +package io.papermc.testplugin; import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; public final class TestPlugin extends JavaPlugin implements Listener { + @Override public void onEnable() { this.getServer().getPluginManager().registerEvents(this, this); diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java new file mode 100644 index 0000000000..e464dac8ae --- /dev/null +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginBootstrap.java @@ -0,0 +1,13 @@ +package io.papermc.testplugin; + +import io.papermc.paper.plugin.bootstrap.PluginBootstrap; +import io.papermc.paper.plugin.bootstrap.PluginProviderContext; +import org.jetbrains.annotations.NotNull; + +public class TestPluginBootstrap implements PluginBootstrap { + + @Override + public void bootstrap(@NotNull PluginProviderContext context) { + } + +} diff --git a/test-plugin/src/main/java/io/papermc/testplugin/TestPluginLoader.java b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginLoader.java new file mode 100644 index 0000000000..084899a9fe --- /dev/null +++ b/test-plugin/src/main/java/io/papermc/testplugin/TestPluginLoader.java @@ -0,0 +1,11 @@ +package io.papermc.testplugin; + +import io.papermc.paper.plugin.loader.PluginClasspathBuilder; +import io.papermc.paper.plugin.loader.PluginLoader; +import org.jetbrains.annotations.NotNull; + +public class TestPluginLoader implements PluginLoader { + @Override + public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) { + } +} diff --git a/test-plugin/src/main/resources/paper-plugin.yml b/test-plugin/src/main/resources/paper-plugin.yml new file mode 100644 index 0000000000..459345d794 --- /dev/null +++ b/test-plugin/src/main/resources/paper-plugin.yml @@ -0,0 +1,12 @@ +name: Paper-Test-Plugin +version: ${version} +main: io.papermc.testplugin.TestPlugin +description: Paper Test Plugin +author: PaperMC +api-version: ${apiversion} +load: STARTUP +bootstrapper: io.papermc.testplugin.TestPluginBootstrap +loader: io.papermc.testplugin.TestPluginLoader +defaultPerm: FALSE +permissions: +dependencies: diff --git a/test-plugin/src/main/resources/plugin.yml b/test-plugin/src/main/resources/plugin.yml deleted file mode 100644 index 1e6adb9ff6..0000000000 --- a/test-plugin/src/main/resources/plugin.yml +++ /dev/null @@ -1,7 +0,0 @@ -name: Paper-Test-Plugin -version: ${version} -main: io.papermc.paper.testplugin.TestPlugin -description: Paper Test Plugin -author: PaperMC -api-version: ${apiversion} -load: STARTUP