Paper/patches/api/0009-Paper-Plugins.patch
Bjarne Koll 4d20922b79
Updated Upstream (Bukkit/CraftBukkit/Spigot) (#11024)
* Updated Upstream (Bukkit/CraftBukkit/Spigot)

Upstream has released updates that appear to apply and compile correctly.
This update has not been tested by PaperMC and as with ANY update, please do your own testing

Bukkit Changes:
e86f4dc4 PR-1041: Improve getPlayer(String) docs to clarify it matches the name
9738f005 Fix spawner API documentation
69ebd9fd PR-1034: Add TrialSpawnerSpawnEvent
23cffd9c PR-973: Improve spawner API and add API for Trial Spawners
8bf19163 PR-1038: Clarify HumanEntity#openInventory(InventoryView) JavaDoc
1ff76351 SPIGOT-7732, SPIGOT-7786: Add freezing damage modifier
02161cb4 PR-1034: Add CreatureSpawnEvent.SpawnReason#TRIAL_SPAWNER
f9cb6f34 SPIGOT-7777: All entity potion effects are removed on death
25d548eb PR-1031: Expose Creeper igniter
ccbf0915 SPIGOT-7770: Reserve spaces in shaped recipes for blank ingredients
17f7097c Clarify ambiguity around what is API
71714f0c Remove note from InventoryView JavaDoc
aaf49731 PR-1030: Deprecate more unused methods in UnsafeValues
3a9dc689 SPIGOT-7771: Material.getDefaultAttributes always returns an empty map

CraftBukkit Changes:
c3ceeb6f7 SPIGOT-7814: Call PlayerShearEntityEvent for Bogged
97b1e4f58 Fix wolf armor not dropping from use of shears
fd2ef563a SPIGOT-7812: Revert "SPIGOT-7809: Restore shield/banner conversion for base colours"
f672c351b SPIGOT-7811: Enchantments are applied on sweeping attack even if damage event is cancelled
cfe29350b SPIGOT-7808: Fix implementation of Enchantment#getName() for bad name return
19335f69e SPIGOT-7809: Restore shield/banner conversion for base colours
ae4f5a0be SPIGOT-7805: Fix jukebox deserialization
62e3b73a4 SPIGOT-7804: Fix written book serialization
aac911d26 SPIGOT-7800, SPIGOT-7801: Keep vanilla behaviour for items dropped on player death
13ece474f PR-1429: Implement TrialSpawnerSpawnEvent
bf13e9113 PR-1354: Improve spawner API and add API for Trial Spawners
515fe49e1 Increase outdated build delay
9cd5a19a0 SPIGOT-7794: Cancelling InventoryItemMoveEvent destroys items
ce40c7b14 SPIGOT-7796: Kickplayer newlines not working
5167256ff SPIGOT-7795: Fix damage/stats ignore the invulnerable damage time
f993563ee Improve cross-world teleportation handling
ab29122cf PR-1433: HumanEntity#openInventory(InventoryView) should only support views belonging to the player
764a541c5 SPIGOT-7732: Issue with the "hurt()" method of EntityLiving and invulnerable time
820084b5f SPIGOT-7791: Skull BlockState with null profile causes NullPointerException
5e46f1c82 SPIGOT-7785: Teleporting a player at the right moment can mess up vanilla teleportation
cbd95a6b3 SPIGOT-7772: Include hidden / non-sampled players in player count
3153debc5 SPIGOT-7790: Server crashes after bee nest is forced to update
e77bb26bb SPIGOT-7788: The healing power of friendship advancement is never granted
ee3d7258a SPIGOT-7789: Fix NPE in CraftMetaFirework applyToItem
2889b3a11 PR-1429: Add CreatureSpawnEvent.SpawnReason#TRIAL_SPAWNER
cdd05bc7f SPIGOT-7777: Speed attribute stays after death; missing EntityPotionEffectEvent call
d0e6af2d4 PR-1428: Expose Creeper igniter
d01c70e93 PR-1425: Fix bytecode transformation taking care of class-to-interface compatibility.
b2b08f68c SPIGOT-7770: Fix certain shaped recipes not registering
3f8e4161f PR-1426: Deprecate more unused methods in UnsafeValues
2c9dd879e SPIGOT-7771: Material.getDefaultAttributes always returns an empty map

Spigot Changes:
491f3675 Rebuild patches
0a642bd7 Rebuild patches
8897571b Rebuild patches
cb8cf80c Fix newlines in custom restart message
1aabe506 Rebuild patches
2024-07-06 21:19:14 +02:00

2573 lines
114 KiB
Diff

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 ed0b67ac322aa22b191cd35502ae5b4f20af19f8..258d7010d24c529c9bbc76cc26adf226c641ee58 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -52,7 +52,7 @@ dependencies {
implementation("org.ow2.asm:asm-commons:9.7")
// Paper end
- compileOnly("org.apache.maven:maven-resolver-provider:3.9.6")
+ api("org.apache.maven:maven-resolver-provider:3.9.6") // Paper - make API dependency for Paper Plugins
compileOnly("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.18")
compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18")
@@ -139,6 +139,7 @@ tasks.withType<Javadoc> {
"https://jd.advntr.dev/text-serializer-plain/$adventureVersion/",
"https://jd.advntr.dev/text-logger-slf4j/$adventureVersion/",
// Paper end
+ "https://javadoc.io/doc/org.apache.maven.resolver/maven-resolver-api/1.7.3", // Paper
)
options.tags("apiNote:a:API Note:")
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..cdbc93b317b3bab47bf6552c29cfbb2c27846933
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/PermissionManager.java
@@ -0,0 +1,171 @@
+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/BootstrapContext.java b/src/main/java/io/papermc/paper/plugin/bootstrap/BootstrapContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..08f2050356acaf74e3210416760e3873c2dafd2c
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/bootstrap/BootstrapContext.java
@@ -0,0 +1,14 @@
+package io.papermc.paper.plugin.bootstrap;
+
+import org.jetbrains.annotations.ApiStatus;
+
+/**
+ * Represents the context provided to a {@link PluginBootstrap} during both the bootstrapping and plugin
+ * instantiation 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.Experimental
+@ApiStatus.NonExtendable
+public interface BootstrapContext extends PluginProviderContext {
+}
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..288c078da3d3ca78d02caa4e3565ac7cf89f9f9f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginBootstrap.java
@@ -0,0 +1,41 @@
+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 BootstrapContext 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..2c14693155de3654d5ca011c63e13e4a1abf2080
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContext.java
@@ -0,0 +1,52 @@
+package io.papermc.paper.plugin.bootstrap;
+
+import io.papermc.paper.plugin.configuration.PluginMeta;
+import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import java.nio.file.Path;
+
+/**
+ * Represents the context provided to a {@link PluginBootstrap} during both the bootstrapping and plugin
+ * instantiation 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
+ ComponentLogger getLogger();
+
+ /**
+ * Provides the path to the originating source of the plugin, such as the plugin's JAR file.
+ *
+ * @return the previously described path
+ */
+ @NotNull
+ Path getPluginSource();
+
+}
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..bcf91d048d84144f6acf9bfd2095df9ada2e585f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/configuration/PluginMeta.java
@@ -0,0 +1,203 @@
+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 semantic 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..ddb3076124365d0d1a5caa32d4dcb1f4314dd7ae
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/configuration/package-info.java
@@ -0,0 +1,8 @@
+/**
+ * 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..28cbc09b7c1ded1f4515969cef4a669adac85703
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/loader/PluginClasspathBuilder.java
@@ -0,0 +1,38 @@
+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..c9e31f78ff6ff969436c6d99755845786c4d383f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/loader/PluginLoader.java
@@ -0,0 +1,30 @@
+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..1347b535d90c2c281c184d0459e7ac59c0350c9f
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/loader/library/ClassPathLibrary.java
@@ -0,0 +1,20 @@
+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..79ba423a364b50588f3ee87fdc69155cb8e64ad0
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/loader/library/LibraryLoadingException.java
@@ -0,0 +1,15 @@
+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..0546fa1e9dcd7155086a8650806a8c086b6fc458
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/loader/library/LibraryStore.java
@@ -0,0 +1,26 @@
+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..e3da0d67cab01e1233dccde1a12ff25961ee79fb
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/loader/library/impl/JarLibrary.java
@@ -0,0 +1,45 @@
+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 the file does not exist 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..70f352630de71f575d1aea5a3126da19a94791ab
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/loader/library/impl/MavenLibraryResolver.java
@@ -0,0 +1,133 @@
+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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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 = LoggerFactory.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.setSystemProperties(System.getProperties());
+ 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.info("Downloading {}", 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..64e46fdfa4d404cb08c67a456e5990b729296b98
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/ClassLoaderAccess.java
@@ -0,0 +1,34 @@
+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..a21bdc57564aef7caf43dde3b2bcb2fc7f30461c
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/ConfiguredPluginClassLoader.java
@@ -0,0 +1,71 @@
+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;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * 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);
+
+ /**
+ * Gets the plugin held by this class loader.
+ *
+ * @return the plugin or null if it doesn't exist yet
+ */
+ @Nullable JavaPlugin getPlugin();
+
+ /**
+ * Get the plugin classloader group
+ * that is used by the underlying classloader
+ * @return classloader
+ */
+ @Nullable
+ PluginClassLoaderGroup getGroup();
+}
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..fefc87d5ffd36b848a7adb326a3a741e9edb28df
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorage.java
@@ -0,0 +1,92 @@
+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..2c0e5ba6f8eba7a632180491843071b8a8558e56
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/PaperClassLoaderStorageAccess.java
@@ -0,0 +1,17 @@
+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..885151cb932d9b8c09a7887edc879e154225f416
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/classloader/PluginClassLoaderGroup.java
@@ -0,0 +1,65 @@
+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..44d630c3eb2670c36134b9907519dc986b3761b4
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/entrypoint/DependencyContext.java
@@ -0,0 +1,48 @@
+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/ProviderUtil.java b/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..6bf3d212a6156ad9ab0e82d1ca0a04f83f6e4b83
--- /dev/null
+++ b/src/main/java/io/papermc/paper/plugin/provider/util/ProviderUtil.java
@@ -0,0 +1,78 @@
+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 9ba1a4e838538ecd55f4f8e50ffb0c5f1f474382..d8b346fe0f9634218954fe818d53272a0896af9c 100644
--- a/src/main/java/org/bukkit/UnsafeValues.java
+++ b/src/main/java/org/bukkit/UnsafeValues.java
@@ -141,4 +141,14 @@ public interface UnsafeValues {
@ApiStatus.Internal
<B extends Keyed> B get(Registry<B> registry, NamespacedKey key);
+
+ // 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 1dbbc244309043b18c1d71707c4fb066c0d0e02d..551c5af6a7bfa2268cbc63be8e70d129bccaa912 100644
--- a/src/main/java/org/bukkit/command/PluginCommand.java
+++ b/src/main/java/org/bukkit/command/PluginCommand.java
@@ -14,7 +14,7 @@ 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 fd5a7a55484deb3fdcced7ebd1f4f6c14d5b4f4f..9207ae900cb4cc8ce41dd4e63d7ad8b35b0ac048 100644
--- a/src/main/java/org/bukkit/command/SimpleCommandMap.java
+++ b/src/main/java/org/bukkit/command/SimpleCommandMap.java
@@ -35,7 +35,7 @@ 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/command/defaults/PluginsCommand.java b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
index bcb576a4271b1ec7b1cfe6f83cf161b7d89ed2e5..4d849e1283fdf6b0872a0f2183964cc93748c116 100644
--- a/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
+++ b/src/main/java/org/bukkit/command/defaults/PluginsCommand.java
@@ -9,6 +9,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
+@Deprecated(forRemoval = true) // Paper
public class PluginsCommand extends BukkitCommand {
public PluginsCommand(@NotNull String name) {
super(name);
diff --git a/src/main/java/org/bukkit/plugin/Plugin.java b/src/main/java/org/bukkit/plugin/Plugin.java
index b37938745f916b5f0111b07b1a1c97527f026e9d..8c76716249e44ed8bf6be94c1f5c7b6d9bb35be2 100644
--- a/src/main/java/org/bukkit/plugin/Plugin.java
+++ b/src/main/java/org/bukkit/plugin/Plugin.java
@@ -30,10 +30,21 @@ 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"
@@ -94,6 +105,7 @@ 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 94f8ceb965cecb5669a84a0ec61c0f706c2a2673..e773db6da357ad210eb24d4c389af2dc84ce450a 100644
--- a/src/main/java/org/bukkit/plugin/PluginBase.java
+++ b/src/main/java/org/bukkit/plugin/PluginBase.java
@@ -31,6 +31,6 @@ 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 8e44f7eaf960884b09ac8413c4383fe17e54b584..c5465431ce35d264d8510af45e73d058b333c60b 100644
--- a/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java
+++ b/src/main/java/org/bukkit/plugin/PluginDescriptionFile.java
@@ -199,7 +199,7 @@ import org.yaml.snakeyaml.representer.Representer;
* 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
@@ -260,6 +260,70 @@ 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 a88733f1cd1ddb5d85ab1b0e6af4fd5b80bbc1c6..cb530369e667c426c842da356c31304bb5c3ecfa 100644
--- a/src/main/java/org/bukkit/plugin/PluginLoader.java
+++ b/src/main/java/org/bukkit/plugin/PluginLoader.java
@@ -12,6 +12,7 @@ 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 ae3e68562c29992fab627428db3ff0006d8216f9..47153dee66782a00b980ecf15e8774ab6f3d887d 100644
--- a/src/main/java/org/bukkit/plugin/PluginManager.java
+++ b/src/main/java/org/bukkit/plugin/PluginManager.java
@@ -14,7 +14,7 @@ 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
@@ -23,6 +23,7 @@ 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;
/**
@@ -312,4 +313,17 @@ 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 839130f713e9a1862e1026590a76ac027c00cab8..46c7be5fa69f13900860b9944523beea16f2409b 100644
--- a/src/main/java/org/bukkit/plugin/SimplePluginManager.java
+++ b/src/main/java/org/bukkit/plugin/SimplePluginManager.java
@@ -44,6 +44,8 @@ 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>();
@@ -52,10 +54,13 @@ 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) {
@@ -112,6 +117,11 @@ 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");
@@ -130,6 +140,7 @@ public final class SimplePluginManager implements PluginManager {
*/
@NotNull
public Plugin[] loadPlugins(@NotNull File[] files) {
+ // TODO Replace with Paper plugin loader
Preconditions.checkArgument(files != null, "File list cannot be null");
List<Plugin> result = new ArrayList<Plugin>();
@@ -390,6 +401,15 @@ 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);
@@ -440,12 +460,14 @@ 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()]);
}
@@ -459,6 +481,7 @@ 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);
@@ -472,6 +495,7 @@ 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 {
@@ -481,6 +505,7 @@ 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);
@@ -500,6 +525,7 @@ 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]);
@@ -508,6 +534,7 @@ 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);
@@ -552,6 +579,7 @@ public final class SimplePluginManager implements PluginManager {
@Override
public void clearPlugins() {
+ if (true) {this.paperPluginManager.clearPlugins(); return;} // Paper
synchronized (this) {
disablePlugins();
plugins.clear();
@@ -572,6 +600,7 @@ 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.");
@@ -620,6 +649,7 @@ 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");
}
@@ -653,6 +683,7 @@ 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, ignoreCancelled); return;} // Paper
if (!plugin.isEnabled()) {
throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled");
@@ -700,16 +731,19 @@ 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(Locale.ROOT));
}
@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(Locale.ROOT);
if (permissions.containsKey(name)) {
@@ -723,21 +757,25 @@ 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(Locale.ROOT));
}
@Override
public void recalculatePermissionDefaults(@NotNull Permission perm) {
+ if (true) {this.paperPluginManager.recalculatePermissionDefaults(perm); return;} // Paper
if (perm != null && permissions.containsKey(perm.getName().toLowerCase(Locale.ROOT))) {
defaultPerms.get(true).remove(perm);
defaultPerms.get(false).remove(perm);
@@ -777,6 +815,7 @@ 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(Locale.ROOT);
Map<Permissible, Boolean> map = permSubs.get(name);
@@ -790,6 +829,7 @@ 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(Locale.ROOT);
Map<Permissible, Boolean> map = permSubs.get(name);
@@ -805,6 +845,7 @@ 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(Locale.ROOT);
Map<Permissible, Boolean> map = permSubs.get(name);
@@ -817,6 +858,7 @@ 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) {
@@ -829,6 +871,7 @@ 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) {
@@ -843,6 +886,7 @@ 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) {
@@ -855,6 +899,7 @@ 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());
}
@@ -878,6 +923,7 @@ public final class SimplePluginManager implements PluginManager {
@Override
public boolean useTimings() {
+ if (true) {return this.paperPluginManager.useTimings();} // Paper
return useTimings;
}
@@ -889,4 +935,28 @@ 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 a80251eff75430863b37db1c131e22593f3fcd5e..310c4041963a3f1e0a26e39a6da12a9bfdb51edc 100644
--- a/src/main/java/org/bukkit/plugin/UnknownDependencyException.java
+++ b/src/main/java/org/bukkit/plugin/UnknownDependencyException.java
@@ -43,4 +43,16 @@ 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 7fca39df009308adad55a6e9dc10a4a0dead86f2..2a14522c484febcd880d00197df4359a0020dddd 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPlugin.java
@@ -41,6 +41,7 @@ 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;
@@ -49,13 +50,16 @@ 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) {
@@ -80,9 +84,12 @@ 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;
}
@@ -123,13 +130,20 @@ 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;
}
+ @NotNull
+ public final io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
+ return this.pluginMeta;
+ }
+
@NotNull
@Override
public FileConfiguration getConfig() {
@@ -259,7 +273,8 @@ 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;
@@ -271,9 +286,18 @@ 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
+ private static class DummyPluginLoaderImplHolder {
+ private static final PluginLoader INSTANCE = net.kyori.adventure.util.Services.service(PluginLoader.class)
+ .orElseThrow();
+ }
+ 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 = DummyPluginLoaderImplHolder.INSTANCE; // Paper
this.server = server;
this.file = file;
this.description = description;
@@ -281,6 +305,7 @@ 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
}
/**
@@ -397,10 +422,10 @@ public abstract class JavaPlugin extends PluginBase {
throw new IllegalArgumentException(clazz + " does not extend " + JavaPlugin.class);
}
final ClassLoader cl = clazz.getClassLoader();
- if (!(cl instanceof PluginClassLoader)) {
- throw new IllegalArgumentException(clazz + " is not initialized by " + PluginClassLoader.class);
+ if (!(cl instanceof io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader configuredPluginClassLoader)) { // Paper
+ throw new IllegalArgumentException(clazz + " is not initialized by a " + io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader.class); // Paper
}
- JavaPlugin plugin = ((PluginClassLoader) cl).plugin;
+ JavaPlugin plugin = configuredPluginClassLoader.getPlugin(); // Paper
if (plugin == null) {
throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer");
}
@@ -423,10 +448,10 @@ public abstract class JavaPlugin extends PluginBase {
public static JavaPlugin getProvidingPlugin(@NotNull Class<?> clazz) {
Preconditions.checkArgument(clazz != null, "Null class cannot have a plugin");
final ClassLoader cl = clazz.getClassLoader();
- if (!(cl instanceof PluginClassLoader)) {
- throw new IllegalArgumentException(clazz + " is not provided by " + PluginClassLoader.class);
+ if (!(cl instanceof io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader configuredPluginClassLoader)) { // Paper
+ throw new IllegalArgumentException(clazz + " is not provided by a " + io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader.class); // Paper
}
- JavaPlugin plugin = ((PluginClassLoader) cl).plugin;
+ JavaPlugin plugin = configuredPluginClassLoader.getPlugin(); // Paper
if (plugin == null) {
throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer");
}
diff --git a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
index 047c0304fd617cec990f80815b43916c6ef5a94c..ab04ffe4cd05315a2ee0f64c553b4c674740eb7f 100644
--- a/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/JavaPluginLoader.java
@@ -49,6 +49,7 @@ 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$")};
@@ -79,6 +80,7 @@ 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()) {
@@ -142,7 +144,7 @@ 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, 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 160f8c348271154c672adf936b358ffeb3b63e69..f4d655a158410039305ac68cebe0d79000f73df8 100644
--- a/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/LibraryLoader.java
@@ -36,7 +36,10 @@ 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;
@@ -55,6 +58,7 @@ class LibraryLoader
this.repository = locator.getService( RepositorySystem.class );
this.session = MavenRepositorySystemUtils.newSession();
+ session.setSystemProperties(System.getProperties()); // Paper - paper plugins, backport system properties fix for transitive dependency parsing, see #10116
session.setChecksumPolicy( RepositoryPolicy.CHECKSUM_POLICY_FAIL );
session.setLocalRepositoryManager( repository.newLocalRepositoryManager( session, new LocalRepository( "libraries" ) ) );
session.setTransferListener( new AbstractTransferListener()
@@ -84,7 +88,7 @@ class LibraryLoader
}
logger.log( Level.INFO, "[{0}] Loading {1} libraries... please wait", new Object[]
{
- desc.getName(), desc.getLibraries().size()
+ java.util.Objects.requireNonNullElseGet(desc.getPrefix(), desc::getName), desc.getLibraries().size() // Paper - use configured log prefix
} );
List<Dependency> dependencies = new ArrayList<>();
@@ -122,7 +126,7 @@ class LibraryLoader
jarFiles.add( url );
logger.log( Level.INFO, "[{0}] Loaded library {1}", new Object[]
{
- desc.getName(), file
+ java.util.Objects.requireNonNullElseGet(desc.getPrefix(), desc::getName), file // Paper - use configured log prefix
} );
}
diff --git a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
index 64a294aeb6fb548794708b38c3707f9dd882b2ff..58d20eff7e0da2d7fcb1609d55e4284715355634 100644
--- a/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
+++ b/src/main/java/org/bukkit/plugin/java/PluginClassLoader.java
@@ -31,7 +31,8 @@ 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;
@@ -45,24 +46,32 @@ final class PluginClassLoader extends URLClassLoader {
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, JarFile jarFile, io.papermc.paper.plugin.provider.entrypoint.DependencyContext dependencyContext) throws IOException, InvalidPluginException, MalformedURLException { // Paper - use JarFile provided by SpigotPluginProvider
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;
- this.jar = new JarFile(file);
+ this.jar = jarFile; // Paper - use JarFile provided by SpigotPluginProvider
this.manifest = jar.getManifest();
this.url = file.toURI().toURL();
this.libraryLoader = libraryLoader;
+ // Paper start
+ this.dependencyContext = dependencyContext;
+ this.classLoaderGroup = io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage.instance().registerSpigotGroup(this);
+ // Paper end
+
Class<?> jarClass;
try {
jarClass = Class.forName(description.getMain(), true, this);
@@ -107,6 +116,27 @@ 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);
+ }
+
+ @Override
+ public JavaPlugin getPlugin() {
+ return this.plugin;
+ }
+ // Paper end
+
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
return loadClass0(name, resolve, true, true);
@@ -132,26 +162,11 @@ 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.
- if (result.getClassLoader() instanceof PluginClassLoader) {
- PluginDescriptionFile provider = ((PluginClassLoader) result.getClassLoader()).description;
-
- if (provider != description
- && !seenIllegalAccess.contains(provider.getName())
- && !((SimplePluginManager) loader.server.getPluginManager()).isTransitiveDepend(description, provider)) {
-
- seenIllegalAccess.add(provider.getName());
- if (plugin != null) {
- plugin.getLogger().log(Level.WARNING, "Loaded class {0} from {1} which is not a depend or softdepend of this plugin.", new Object[]{name, provider.getFullName()});
- } else {
- // 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()});
- }
- }
- }
+ // Paper - Totally delete the illegal access logic, we are never going to enforce it anyways here.
return result;
}
@@ -180,7 +195,7 @@ 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) {
@@ -210,8 +225,8 @@ final class PluginClassLoader extends URLClassLoader {
result = super.findClass(name);
}
- loader.setClass(name, result);
classes.put(name, result);
+ this.setClass(name, result); // Paper
}
return result;
@@ -220,6 +235,12 @@ 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();
@@ -231,7 +252,7 @@ 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) {
@@ -241,6 +262,38 @@ 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);
+ }
}
+
+ @Override
+ public @Nullable io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup getGroup() {
+ return this.classLoaderGroup;
+ }
+
+ // Paper end
}
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 40a086f2883c4419d2bf0bd44285f7c55562ba3e..0000000000000000000000000000000000000000
--- a/src/test/java/org/bukkit/event/SyntheticEventTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package org.bukkit.event;
-
-import static org.junit.jupiter.api.Assertions.*;
-import org.bukkit.Bukkit;
-import org.bukkit.plugin.PluginLoader;
-import org.bukkit.plugin.SimplePluginManager;
-import org.bukkit.plugin.TestPlugin;
-import org.bukkit.plugin.java.JavaPluginLoader;
-import org.bukkit.support.AbstractTestingBase;
-import org.junit.jupiter.api.Test;
-
-public class SyntheticEventTest extends AbstractTestingBase {
- @SuppressWarnings("deprecation")
- @Test
- public void test() {
- final JavaPluginLoader loader = new JavaPluginLoader(Bukkit.getServer());
- TestPlugin plugin = new TestPlugin(getClass().getName()) {
- @Override
- public PluginLoader getPluginLoader() {
- return loader;
- }
- };
- SimplePluginManager pluginManager = new SimplePluginManager(Bukkit.getServer(), null);
-
- TestEvent event = new TestEvent(false);
- Impl impl = new Impl();
-
- pluginManager.registerEvents(impl, plugin);
- pluginManager.callEvent(event);
-
- 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 03b08e47e91e8b56c1992fcd749a62eb9e7d4d68..0000000000000000000000000000000000000000
--- a/src/test/java/org/bukkit/plugin/PluginManagerTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package org.bukkit.plugin;
-
-import static org.bukkit.support.MatcherAssert.*;
-import static org.hamcrest.Matchers.*;
-import org.bukkit.Bukkit;
-import org.bukkit.event.Event;
-import org.bukkit.event.TestEvent;
-import org.bukkit.permissions.Permission;
-import org.bukkit.support.AbstractTestingBase;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Test;
-
-public class PluginManagerTest extends AbstractTestingBase {
- private class MutableObject {
- volatile Object value = null;
- }
-
- private static final PluginManager pm = Bukkit.getServer().getPluginManager();
-
- 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(pm.getPermission(name), is(perm), "Permission \"" + name + "\" was not added");
- pm.removePermission(name);
- assertThat(pm.getPermission(name), is(nullValue()), "Permission \"" + name + "\" was not removed");
- }
-
- private void testRemovePermissionByPermission(final String name) {
- final Permission perm = new Permission(name);
- pm.addPermission(perm);
- assertThat(pm.getPermission(name), is(perm), "Permission \"" + name + "\" was not added");
- pm.removePermission(perm);
- assertThat(pm.getPermission(name), is(nullValue()), "Permission \"" + name + "\" was not removed");
- }
-
- @AfterEach
- 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 a8be3e23e3e280ad301d9530de50028515612966..43b58e920e739bb949ac0673e9ef73ba7b500dc9 100644
--- a/src/test/java/org/bukkit/plugin/TestPlugin.java
+++ b/src/test/java/org/bukkit/plugin/TestPlugin.java
@@ -32,6 +32,12 @@ 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/src/test/java/org/bukkit/support/TestServer.java b/src/test/java/org/bukkit/support/TestServer.java
index a47ee3ce660ec4467b5ed6a4b41fb2d19179a189..c79faf4197f9c0a7256cefe2b001182102d2b796 100644
--- a/src/test/java/org/bukkit/support/TestServer.java
+++ b/src/test/java/org/bukkit/support/TestServer.java
@@ -26,8 +26,7 @@ public final class TestServer {
Thread creatingThread = Thread.currentThread();
when(instance.isPrimaryThread()).then(mock -> Thread.currentThread().equals(creatingThread));
- PluginManager pluginManager = new SimplePluginManager(instance, new SimpleCommandMap(instance));
- when(instance.getPluginManager()).thenReturn(pluginManager);
+ // Paper - remove plugin manager for Paper Plugins
Logger logger = Logger.getLogger(TestServer.class.getCanonicalName());
when(instance.getLogger()).thenReturn(logger);