Paper Plugins
This commit is contained in:
@@ -66,7 +66,7 @@ dependencies {
|
|||||||
implementation("org.ow2.asm:asm-commons:9.7.1")
|
implementation("org.ow2.asm:asm-commons:9.7.1")
|
||||||
// Paper end
|
// 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-connector-basic:1.9.18")
|
||||||
compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18")
|
compileOnly("org.apache.maven.resolver:maven-resolver-transport-http:1.9.18")
|
||||||
|
|
||||||
@@ -156,6 +156,7 @@ tasks.withType<Javadoc> {
|
|||||||
"https://jd.advntr.dev/text-serializer-plain/$adventureVersion/",
|
"https://jd.advntr.dev/text-serializer-plain/$adventureVersion/",
|
||||||
"https://jd.advntr.dev/text-logger-slf4j/$adventureVersion/",
|
"https://jd.advntr.dev/text-logger-slf4j/$adventureVersion/",
|
||||||
// Paper end
|
// Paper end
|
||||||
|
"https://javadoc.io/doc/org.apache.maven.resolver/maven-resolver-api/1.7.3", // Paper
|
||||||
)
|
)
|
||||||
options.tags("apiNote:a:API Note:")
|
options.tags("apiNote:a:API Note:")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package io.papermc.paper.plugin;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.bukkit.permissions.Permissible;
|
||||||
|
import org.bukkit.permissions.Permission;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A permission manager implementation to keep backwards compatibility partially alive with existing plugins that used
|
||||||
|
* the bukkit one before.
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
@NullMarked
|
||||||
|
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(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(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(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(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the default permissions for the given op status
|
||||||
|
*
|
||||||
|
* @param op Which set of default permissions to get
|
||||||
|
* @return The default permissions
|
||||||
|
*/
|
||||||
|
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(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(String permission, 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(String permission, 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
|
||||||
|
*/
|
||||||
|
Set<Permissible> getPermissionSubscriptions(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, 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, 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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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(List<Permission> perm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the current registered permissinos.
|
||||||
|
* <p>
|
||||||
|
* This is used for reloading.
|
||||||
|
*/
|
||||||
|
void clearPermissions();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package io.papermc.paper.plugin.bootstrap;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the context provided to a {@link PluginBootstrap} during both the bootstrapping and plugin
|
||||||
|
* instantiation logic.
|
||||||
|
* A bootstrap 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
|
||||||
|
@NullMarked
|
||||||
|
@ApiStatus.NonExtendable
|
||||||
|
public interface BootstrapContext extends PluginProviderContext {
|
||||||
|
}
|
||||||
@@ -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.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A plugin bootstrap 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.Experimental
|
||||||
|
@NullMarked
|
||||||
|
@ApiStatus.OverrideOnly
|
||||||
|
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(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.
|
||||||
|
*/
|
||||||
|
default JavaPlugin createPlugin(final PluginProviderContext context) {
|
||||||
|
return ProviderUtil.loadClass(context.getConfiguration().getMainClass(), JavaPlugin.class, this.getClass().getClassLoader());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package io.papermc.paper.plugin.bootstrap;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the context provided to a {@link PluginBootstrap} during both the bootstrapping and plugin
|
||||||
|
* instantiation logic.
|
||||||
|
* A bootstrap 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
|
||||||
|
@NullMarked
|
||||||
|
@ApiStatus.NonExtendable
|
||||||
|
public interface PluginProviderContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the plugin's configuration.
|
||||||
|
*
|
||||||
|
* @return the plugin's configuration
|
||||||
|
*/
|
||||||
|
PluginMeta getConfiguration();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the path to the data directory of the plugin.
|
||||||
|
*
|
||||||
|
* @return the previously described path
|
||||||
|
*/
|
||||||
|
Path getDataDirectory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the logger used for this plugin.
|
||||||
|
*
|
||||||
|
* @return the logger instance
|
||||||
|
*/
|
||||||
|
ComponentLogger getLogger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the path to the originating source of the plugin, such as the plugin's JAR file.
|
||||||
|
*
|
||||||
|
* @return the previously described path
|
||||||
|
*/
|
||||||
|
Path getPluginSource();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package io.papermc.paper.plugin.configuration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
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.jspecify.annotations.NullMarked;
|
||||||
|
import org.jspecify.annotations.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class acts as an abstraction for a plugin configuration.
|
||||||
|
*/
|
||||||
|
@ApiStatus.Experimental // Subject to change!
|
||||||
|
@NullMarked
|
||||||
|
@ApiStatus.NonExtendable
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the display name of the plugin, including the version.
|
||||||
|
*
|
||||||
|
* @return a descriptive name of the plugin and respective version
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
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.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.Experimental
|
||||||
|
@NullMarked
|
||||||
|
@ApiStatus.NonExtendable
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@Contract("_ -> this")
|
||||||
|
PluginClasspathBuilder addLibrary(ClassPathLibrary classPathLibrary);
|
||||||
|
|
||||||
|
PluginProviderContext getContext();
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package io.papermc.paper.plugin.loader;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.Experimental
|
||||||
|
@NullMarked
|
||||||
|
@ApiStatus.OverrideOnly
|
||||||
|
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(PluginClasspathBuilder classpathBuilder);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package io.papermc.paper.plugin.loader.library;
|
||||||
|
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The classpath library interface represents libraries that are capable of registering themselves via
|
||||||
|
* {@link #register(LibraryStore)} on any given {@link LibraryStore}.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
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(LibraryStore store) throws LibraryLoadingException;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package io.papermc.paper.plugin.loader.library;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
@NullMarked
|
||||||
|
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(Path library);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
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(final Path path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(final LibraryStore store) throws LibraryLoadingException {
|
||||||
|
if (Files.notExists(this.path)) {
|
||||||
|
throw new LibraryLoadingException("Could not find library at " + this.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
store.addLibrary(this.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
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.jspecify.annotations.NullMarked;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>
|
||||||
|
* Plugins may create and register a {@link MavenLibraryResolver} after configuring it.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
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() {
|
||||||
|
final 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(final 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(final 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(final 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(final LibraryStore store) throws LibraryLoadingException {
|
||||||
|
final List<RemoteRepository> repos = this.repository.newResolutionRepositories(this.session, this.repositories);
|
||||||
|
|
||||||
|
final DependencyResult result;
|
||||||
|
try {
|
||||||
|
result = this.repository.resolveDependencies(this.session, new DependencyRequest(new CollectRequest((Dependency) null, this.dependencies, repos), null));
|
||||||
|
} catch (final DependencyResolutionException ex) {
|
||||||
|
throw new LibraryLoadingException("Error resolving libraries", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final ArtifactResult artifact : result.getArtifactResults()) {
|
||||||
|
final File file = artifact.getArtifact().getFile();
|
||||||
|
store.addLibrary(file.toPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package io.papermc.paper.plugin.provider.classloader;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
@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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package io.papermc.paper.plugin.provider.classloader;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.jspecify.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.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
@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(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();
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package io.papermc.paper.plugin.provider.classloader;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.Contract;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.jspecify.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.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
@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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package io.papermc.paper.plugin.provider.entrypoint;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.configuration.PluginMeta;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
@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(PluginMeta plugin, 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(String pluginIdentifier);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package io.papermc.paper.plugin.provider.util;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.util.SneakyThrow;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jspecify.annotations.NullMarked;
|
||||||
|
import org.jspecify.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.
|
||||||
|
*/
|
||||||
|
@NullMarked
|
||||||
|
@ApiStatus.Internal
|
||||||
|
public final 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
|
||||||
|
*/
|
||||||
|
public static <T> T loadClass(final String clazz, final Class<T> classType, final 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
|
||||||
|
*/
|
||||||
|
public static <T> T loadClass(final String clazz, final Class<T> classType, final ClassLoader loader, final @Nullable Runnable onError) {
|
||||||
|
try {
|
||||||
|
final T clazzInstance;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final Class<?> jarClass = Class.forName(clazz, true, loader);
|
||||||
|
|
||||||
|
final Class<? extends T> pluginClass;
|
||||||
|
try {
|
||||||
|
pluginClass = jarClass.asSubclass(classType);
|
||||||
|
} catch (final ClassCastException ex) {
|
||||||
|
throw new ClassCastException("class '%s' does not extend '%s'".formatted(clazz, classType));
|
||||||
|
}
|
||||||
|
|
||||||
|
clazzInstance = pluginClass.getDeclaredConstructor().newInstance();
|
||||||
|
} catch (final IllegalAccessException exception) {
|
||||||
|
throw new RuntimeException("No public constructor");
|
||||||
|
} catch (final InstantiationException exception) {
|
||||||
|
throw new RuntimeException("Abnormal class instantiation", exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clazzInstance;
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
if (onError != null) {
|
||||||
|
onError.run();
|
||||||
|
}
|
||||||
|
SneakyThrow.sneaky(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError(); // Shouldn't happen
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -146,4 +146,14 @@ public interface UnsafeValues {
|
|||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
Biome getCustomBiome();
|
Biome getCustomBiome();
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public final class PluginCommand extends Command implements PluginIdentifiableCo
|
|||||||
private CommandExecutor executor;
|
private CommandExecutor executor;
|
||||||
private TabCompleter completer;
|
private TabCompleter completer;
|
||||||
|
|
||||||
protected PluginCommand(@NotNull String name, @NotNull Plugin owner) {
|
PluginCommand(@NotNull String name, @NotNull Plugin owner) {
|
||||||
super(name);
|
super(name);
|
||||||
this.executor = owner;
|
this.executor = owner;
|
||||||
this.owningPlugin = owner;
|
this.owningPlugin = owner;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class SimpleCommandMap implements CommandMap {
|
|||||||
private void setDefaultCommands() {
|
private void setDefaultCommands() {
|
||||||
register("bukkit", new VersionCommand("version"));
|
register("bukkit", new VersionCommand("version"));
|
||||||
register("bukkit", new ReloadCommand("reload"));
|
register("bukkit", new ReloadCommand("reload"));
|
||||||
register("bukkit", new PluginsCommand("plugins"));
|
//register("bukkit", new PluginsCommand("plugins")); // Paper
|
||||||
register("bukkit", new TimingsCommand("timings"));
|
register("bukkit", new TimingsCommand("timings"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import org.bukkit.command.CommandSender;
|
|||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@Deprecated(forRemoval = true) // Paper
|
||||||
public class PluginsCommand extends BukkitCommand {
|
public class PluginsCommand extends BukkitCommand {
|
||||||
public PluginsCommand(@NotNull String name) {
|
public PluginsCommand(@NotNull String name) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|||||||
@@ -30,10 +30,21 @@ public interface Plugin extends TabExecutor {
|
|||||||
* Returns the plugin.yaml file containing the details for this plugin
|
* Returns the plugin.yaml file containing the details for this plugin
|
||||||
*
|
*
|
||||||
* @return Contents of the plugin.yaml file
|
* @return Contents of the plugin.yaml file
|
||||||
|
* @deprecated May be inaccurate due to different plugin implementations.
|
||||||
|
* @see Plugin#getPluginMeta()
|
||||||
*/
|
*/
|
||||||
|
@Deprecated // Paper
|
||||||
@NotNull
|
@NotNull
|
||||||
public PluginDescriptionFile getDescription();
|
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
|
* Gets a {@link FileConfiguration} for this plugin, read through
|
||||||
* "config.yml"
|
* "config.yml"
|
||||||
@@ -94,6 +105,7 @@ public interface Plugin extends TabExecutor {
|
|||||||
*
|
*
|
||||||
* @return PluginLoader that controls this plugin
|
* @return PluginLoader that controls this plugin
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future
|
||||||
@NotNull
|
@NotNull
|
||||||
public PluginLoader getPluginLoader();
|
public PluginLoader getPluginLoader();
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ public abstract class PluginBase implements Plugin {
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public final String getName() {
|
public final String getName() {
|
||||||
return getDescription().getName();
|
return getPluginMeta().getName(); // Paper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ import org.yaml.snakeyaml.representer.Representer;
|
|||||||
* inferno.burningdeaths: true
|
* inferno.burningdeaths: true
|
||||||
*</pre></blockquote>
|
*</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 Pattern VALID_NAME = Pattern.compile("^[A-Za-z0-9 _.-]+$");
|
||||||
private static final ThreadLocal<Yaml> YAML = new ThreadLocal<Yaml>() {
|
private static final ThreadLocal<Yaml> YAML = new ThreadLocal<Yaml>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -260,6 +260,70 @@ public final class PluginDescriptionFile {
|
|||||||
private Set<PluginAwareness> awareness = ImmutableSet.of();
|
private Set<PluginAwareness> awareness = ImmutableSet.of();
|
||||||
private String apiVersion = null;
|
private String apiVersion = null;
|
||||||
private List<String> libraries = ImmutableList.of();
|
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 {
|
public PluginDescriptionFile(@NotNull final InputStream stream) throws InvalidDescriptionException {
|
||||||
loadMap(asMap(YAML.get().load(stream)));
|
loadMap(asMap(YAML.get().load(stream)));
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
* Represents a plugin loader, which handles direct access to specific types
|
* Represents a plugin loader, which handles direct access to specific types
|
||||||
* of plugins
|
* of plugins
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true) // Paper - The PluginLoader system will not function in the near future
|
||||||
public interface PluginLoader {
|
public interface PluginLoader {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
/**
|
/**
|
||||||
* Handles all plugin management from the Server
|
* 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
|
* Registers the specified plugin loader
|
||||||
@@ -23,6 +23,7 @@ public interface PluginManager {
|
|||||||
* @throws IllegalArgumentException Thrown when the given Class is not a
|
* @throws IllegalArgumentException Thrown when the given Class is not a
|
||||||
* valid PluginLoader
|
* 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;
|
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
|
* @return True if event timings are to be used
|
||||||
*/
|
*/
|
||||||
public boolean useTimings();
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
/**
|
/**
|
||||||
* Handles all plugin management from the Server
|
* 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 {
|
public final class SimplePluginManager implements PluginManager {
|
||||||
private final Server server;
|
private final Server server;
|
||||||
private final Map<Pattern, PluginLoader> fileAssociations = new HashMap<Pattern, PluginLoader>();
|
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 MutableGraph<String> dependencyGraph = GraphBuilder.directed().build();
|
||||||
private File updateDirectory;
|
private File updateDirectory;
|
||||||
private final SimpleCommandMap commandMap;
|
private final SimpleCommandMap commandMap;
|
||||||
private final Map<String, Permission> permissions = new HashMap<String, Permission>();
|
// Paper start
|
||||||
private final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<Boolean, Set<Permission>>();
|
public final Map<String, Permission> permissions = new HashMap<String, Permission>();
|
||||||
private final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<String, Map<Permissible, Boolean>>();
|
public final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<Boolean, Set<Permission>>();
|
||||||
private final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<Boolean, Map<Permissible, Boolean>>();
|
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;
|
private boolean useTimings = false;
|
||||||
|
|
||||||
public SimplePluginManager(@NotNull Server instance, @NotNull SimpleCommandMap commandMap) {
|
public SimplePluginManager(@NotNull Server instance, @NotNull SimpleCommandMap commandMap) {
|
||||||
@@ -112,6 +117,11 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Plugin[] loadPlugins(@NotNull File directory) {
|
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 != null, "Directory cannot be null");
|
||||||
Preconditions.checkArgument(directory.isDirectory(), "Directory must be a directory");
|
Preconditions.checkArgument(directory.isDirectory(), "Directory must be a directory");
|
||||||
|
|
||||||
@@ -130,6 +140,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public Plugin[] loadPlugins(@NotNull File[] files) {
|
public Plugin[] loadPlugins(@NotNull File[] files) {
|
||||||
|
// TODO Replace with Paper plugin loader
|
||||||
Preconditions.checkArgument(files != null, "File list cannot be null");
|
Preconditions.checkArgument(files != null, "File list cannot be null");
|
||||||
|
|
||||||
List<Plugin> result = new ArrayList<Plugin>();
|
List<Plugin> result = new ArrayList<Plugin>();
|
||||||
@@ -390,6 +401,15 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Nullable
|
@Nullable
|
||||||
public synchronized Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException {
|
public synchronized Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, UnknownDependencyException {
|
||||||
Preconditions.checkArgument(file != null, "File cannot be null");
|
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);
|
checkUpdate(file);
|
||||||
|
|
||||||
@@ -440,12 +460,14 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public synchronized Plugin getPlugin(@NotNull String name) {
|
public synchronized Plugin getPlugin(@NotNull String name) {
|
||||||
|
if (true) {return this.paperPluginManager.getPlugin(name);} // Paper
|
||||||
return lookupNames.get(name.replace(' ', '_'));
|
return lookupNames.get(name.replace(' ', '_'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public synchronized Plugin[] getPlugins() {
|
public synchronized Plugin[] getPlugins() {
|
||||||
|
if (true) {return this.paperPluginManager.getPlugins();} // Paper
|
||||||
return plugins.toArray(new Plugin[plugins.size()]);
|
return plugins.toArray(new Plugin[plugins.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,6 +481,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isPluginEnabled(@NotNull String name) {
|
public boolean isPluginEnabled(@NotNull String name) {
|
||||||
|
if (true) {return this.paperPluginManager.isPluginEnabled(name);} // Paper
|
||||||
Plugin plugin = getPlugin(name);
|
Plugin plugin = getPlugin(name);
|
||||||
|
|
||||||
return isPluginEnabled(plugin);
|
return isPluginEnabled(plugin);
|
||||||
@@ -472,6 +495,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean isPluginEnabled(@Nullable Plugin plugin) {
|
public boolean isPluginEnabled(@Nullable Plugin plugin) {
|
||||||
|
if (true) {return this.paperPluginManager.isPluginEnabled(plugin);} // Paper
|
||||||
if ((plugin != null) && (plugins.contains(plugin))) {
|
if ((plugin != null) && (plugins.contains(plugin))) {
|
||||||
return plugin.isEnabled();
|
return plugin.isEnabled();
|
||||||
} else {
|
} else {
|
||||||
@@ -481,6 +505,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enablePlugin(@NotNull final Plugin plugin) {
|
public void enablePlugin(@NotNull final Plugin plugin) {
|
||||||
|
if (true) {this.paperPluginManager.enablePlugin(plugin); return;} // Paper
|
||||||
if (!plugin.isEnabled()) {
|
if (!plugin.isEnabled()) {
|
||||||
List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin);
|
List<Command> pluginCommands = PluginCommandYamlParser.parse(plugin);
|
||||||
|
|
||||||
@@ -500,6 +525,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disablePlugins() {
|
public void disablePlugins() {
|
||||||
|
if (true) {this.paperPluginManager.disablePlugins(); return;} // Paper
|
||||||
Plugin[] plugins = getPlugins();
|
Plugin[] plugins = getPlugins();
|
||||||
for (int i = plugins.length - 1; i >= 0; i--) {
|
for (int i = plugins.length - 1; i >= 0; i--) {
|
||||||
disablePlugin(plugins[i]);
|
disablePlugin(plugins[i]);
|
||||||
@@ -508,6 +534,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disablePlugin(@NotNull final Plugin plugin) {
|
public void disablePlugin(@NotNull final Plugin plugin) {
|
||||||
|
if (true) {this.paperPluginManager.disablePlugin(plugin); return;} // Paper
|
||||||
if (plugin.isEnabled()) {
|
if (plugin.isEnabled()) {
|
||||||
try {
|
try {
|
||||||
plugin.getPluginLoader().disablePlugin(plugin);
|
plugin.getPluginLoader().disablePlugin(plugin);
|
||||||
@@ -552,6 +579,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearPlugins() {
|
public void clearPlugins() {
|
||||||
|
if (true) {this.paperPluginManager.clearPlugins(); return;} // Paper
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
disablePlugins();
|
disablePlugins();
|
||||||
plugins.clear();
|
plugins.clear();
|
||||||
@@ -572,6 +600,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void callEvent(@NotNull Event event) {
|
public void callEvent(@NotNull Event event) {
|
||||||
|
if (true) {this.paperPluginManager.callEvent(event); return;} // Paper
|
||||||
if (event.isAsynchronous()) {
|
if (event.isAsynchronous()) {
|
||||||
if (Thread.holdsLock(this)) {
|
if (Thread.holdsLock(this)) {
|
||||||
throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
|
throw new IllegalStateException(event.getEventName() + " cannot be triggered asynchronously from inside synchronized code.");
|
||||||
@@ -620,6 +649,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) {
|
public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) {
|
||||||
|
if (true) {this.paperPluginManager.registerEvents(listener, plugin); return;} // Paper
|
||||||
if (!plugin.isEnabled()) {
|
if (!plugin.isEnabled()) {
|
||||||
throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
|
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(priority != null, "Priority cannot be null");
|
||||||
Preconditions.checkArgument(executor != null, "Executor cannot be null");
|
Preconditions.checkArgument(executor != null, "Executor cannot be null");
|
||||||
Preconditions.checkArgument(plugin != null, "Plugin 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()) {
|
if (!plugin.isEnabled()) {
|
||||||
throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled");
|
throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled");
|
||||||
@@ -700,16 +731,19 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Permission getPermission(@NotNull String name) {
|
public Permission getPermission(@NotNull String name) {
|
||||||
|
if (true) {return this.paperPluginManager.getPermission(name);} // Paper
|
||||||
return permissions.get(name.toLowerCase(Locale.ROOT));
|
return permissions.get(name.toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addPermission(@NotNull Permission perm) {
|
public void addPermission(@NotNull Permission perm) {
|
||||||
|
if (true) {this.paperPluginManager.addPermission(perm); return;} // Paper
|
||||||
addPermission(perm, true);
|
addPermission(perm, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(since = "1.12")
|
@Deprecated(since = "1.12")
|
||||||
public void addPermission(@NotNull Permission perm, boolean dirty) {
|
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);
|
String name = perm.getName().toLowerCase(Locale.ROOT);
|
||||||
|
|
||||||
if (permissions.containsKey(name)) {
|
if (permissions.containsKey(name)) {
|
||||||
@@ -723,21 +757,25 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Set<Permission> getDefaultPermissions(boolean op) {
|
public Set<Permission> getDefaultPermissions(boolean op) {
|
||||||
|
if (true) {return this.paperPluginManager.getDefaultPermissions(op);} // Paper
|
||||||
return ImmutableSet.copyOf(defaultPerms.get(op));
|
return ImmutableSet.copyOf(defaultPerms.get(op));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removePermission(@NotNull Permission perm) {
|
public void removePermission(@NotNull Permission perm) {
|
||||||
|
if (true) {this.paperPluginManager.removePermission(perm); return;} // Paper
|
||||||
removePermission(perm.getName());
|
removePermission(perm.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removePermission(@NotNull String name) {
|
public void removePermission(@NotNull String name) {
|
||||||
|
if (true) {this.paperPluginManager.removePermission(name); return;} // Paper
|
||||||
permissions.remove(name.toLowerCase(Locale.ROOT));
|
permissions.remove(name.toLowerCase(Locale.ROOT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void recalculatePermissionDefaults(@NotNull Permission perm) {
|
public void recalculatePermissionDefaults(@NotNull Permission perm) {
|
||||||
|
if (true) {this.paperPluginManager.recalculatePermissionDefaults(perm); return;} // Paper
|
||||||
if (perm != null && permissions.containsKey(perm.getName().toLowerCase(Locale.ROOT))) {
|
if (perm != null && permissions.containsKey(perm.getName().toLowerCase(Locale.ROOT))) {
|
||||||
defaultPerms.get(true).remove(perm);
|
defaultPerms.get(true).remove(perm);
|
||||||
defaultPerms.get(false).remove(perm);
|
defaultPerms.get(false).remove(perm);
|
||||||
@@ -777,6 +815,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
||||||
|
if (true) {this.paperPluginManager.subscribeToPermission(permission, permissible); return;} // Paper
|
||||||
String name = permission.toLowerCase(Locale.ROOT);
|
String name = permission.toLowerCase(Locale.ROOT);
|
||||||
Map<Permissible, Boolean> map = permSubs.get(name);
|
Map<Permissible, Boolean> map = permSubs.get(name);
|
||||||
|
|
||||||
@@ -790,6 +829,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
||||||
|
if (true) {this.paperPluginManager.unsubscribeFromPermission(permission, permissible); return;} // Paper
|
||||||
String name = permission.toLowerCase(Locale.ROOT);
|
String name = permission.toLowerCase(Locale.ROOT);
|
||||||
Map<Permissible, Boolean> map = permSubs.get(name);
|
Map<Permissible, Boolean> map = permSubs.get(name);
|
||||||
|
|
||||||
@@ -805,6 +845,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Set<Permissible> getPermissionSubscriptions(@NotNull String permission) {
|
public Set<Permissible> getPermissionSubscriptions(@NotNull String permission) {
|
||||||
|
if (true) {return this.paperPluginManager.getPermissionSubscriptions(permission);} // Paper
|
||||||
String name = permission.toLowerCase(Locale.ROOT);
|
String name = permission.toLowerCase(Locale.ROOT);
|
||||||
Map<Permissible, Boolean> map = permSubs.get(name);
|
Map<Permissible, Boolean> map = permSubs.get(name);
|
||||||
|
|
||||||
@@ -817,6 +858,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
||||||
|
if (true) {this.paperPluginManager.subscribeToDefaultPerms(op, permissible); return;} // Paper
|
||||||
Map<Permissible, Boolean> map = defSubs.get(op);
|
Map<Permissible, Boolean> map = defSubs.get(op);
|
||||||
|
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
@@ -829,6 +871,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
||||||
|
if (true) {this.paperPluginManager.unsubscribeFromDefaultPerms(op, permissible); return;} // Paper
|
||||||
Map<Permissible, Boolean> map = defSubs.get(op);
|
Map<Permissible, Boolean> map = defSubs.get(op);
|
||||||
|
|
||||||
if (map != null) {
|
if (map != null) {
|
||||||
@@ -843,6 +886,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Set<Permissible> getDefaultPermSubscriptions(boolean op) {
|
public Set<Permissible> getDefaultPermSubscriptions(boolean op) {
|
||||||
|
if (true) {return this.paperPluginManager.getDefaultPermSubscriptions(op);} // Paper
|
||||||
Map<Permissible, Boolean> map = defSubs.get(op);
|
Map<Permissible, Boolean> map = defSubs.get(op);
|
||||||
|
|
||||||
if (map == null) {
|
if (map == null) {
|
||||||
@@ -855,6 +899,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Set<Permission> getPermissions() {
|
public Set<Permission> getPermissions() {
|
||||||
|
if (true) {return this.paperPluginManager.getPermissions();} // Paper
|
||||||
return new HashSet<Permission>(permissions.values());
|
return new HashSet<Permission>(permissions.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -878,6 +923,7 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean useTimings() {
|
public boolean useTimings() {
|
||||||
|
if (true) {return this.paperPluginManager.useTimings();} // Paper
|
||||||
return useTimings;
|
return useTimings;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -889,4 +935,28 @@ public final class SimplePluginManager implements PluginManager {
|
|||||||
public void useTimings(boolean use) {
|
public void useTimings(boolean use) {
|
||||||
useTimings = 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,4 +43,16 @@ public class UnknownDependencyException extends RuntimeException {
|
|||||||
public UnknownDependencyException() {
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public abstract class JavaPlugin extends PluginBase {
|
|||||||
private Server server = null;
|
private Server server = null;
|
||||||
private File file = null;
|
private File file = null;
|
||||||
private PluginDescriptionFile description = null;
|
private PluginDescriptionFile description = null;
|
||||||
|
private io.papermc.paper.plugin.configuration.PluginMeta pluginMeta = null; // Paper
|
||||||
private File dataFolder = null;
|
private File dataFolder = null;
|
||||||
private ClassLoader classLoader = null;
|
private ClassLoader classLoader = null;
|
||||||
private boolean naggable = true;
|
private boolean naggable = true;
|
||||||
@@ -49,13 +50,16 @@ public abstract class JavaPlugin extends PluginBase {
|
|||||||
private PluginLogger logger = null;
|
private PluginLogger logger = null;
|
||||||
|
|
||||||
public JavaPlugin() {
|
public JavaPlugin() {
|
||||||
final ClassLoader classLoader = this.getClass().getClassLoader();
|
// Paper start
|
||||||
if (!(classLoader instanceof PluginClassLoader)) {
|
if (this.getClass().getClassLoader() instanceof io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||||||
throw new IllegalStateException("JavaPlugin requires " + PluginClassLoader.class.getName());
|
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) {
|
protected JavaPlugin(@NotNull final JavaPluginLoader loader, @NotNull final PluginDescriptionFile description, @NotNull final File dataFolder, @NotNull final File file) {
|
||||||
final ClassLoader classLoader = this.getClass().getClassLoader();
|
final ClassLoader classLoader = this.getClass().getClassLoader();
|
||||||
if (classLoader instanceof PluginClassLoader) {
|
if (classLoader instanceof PluginClassLoader) {
|
||||||
@@ -80,9 +84,12 @@ public abstract class JavaPlugin extends PluginBase {
|
|||||||
* Gets the associated PluginLoader responsible for this plugin
|
* Gets the associated PluginLoader responsible for this plugin
|
||||||
*
|
*
|
||||||
* @return PluginLoader that controls 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
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(forRemoval = true) // Paper
|
||||||
public final PluginLoader getPluginLoader() {
|
public final PluginLoader getPluginLoader() {
|
||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
@@ -123,13 +130,20 @@ public abstract class JavaPlugin extends PluginBase {
|
|||||||
* Returns the plugin.yaml file containing the details for this plugin
|
* Returns the plugin.yaml file containing the details for this plugin
|
||||||
*
|
*
|
||||||
* @return Contents of the plugin.yaml file
|
* @return Contents of the plugin.yaml file
|
||||||
|
* @deprecated No longer applicable to all types of plugins
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated
|
||||||
public final PluginDescriptionFile getDescription() {
|
public final PluginDescriptionFile getDescription() {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public final io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
|
||||||
|
return this.pluginMeta;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public FileConfiguration getConfig() {
|
public FileConfiguration getConfig() {
|
||||||
@@ -259,7 +273,8 @@ public abstract class JavaPlugin extends PluginBase {
|
|||||||
*
|
*
|
||||||
* @param enabled true if enabled, otherwise false
|
* @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) {
|
if (isEnabled != enabled) {
|
||||||
isEnabled = enabled;
|
isEnabled = enabled;
|
||||||
|
|
||||||
@@ -271,9 +286,18 @@ public abstract class JavaPlugin extends PluginBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paper start
|
||||||
final void init(@NotNull PluginLoader loader, @NotNull Server server, @NotNull PluginDescriptionFile description, @NotNull File dataFolder, @NotNull File file, @NotNull ClassLoader classLoader) {
|
private static class DummyPluginLoaderImplHolder {
|
||||||
this.loader = loader;
|
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.server = server;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@@ -281,6 +305,7 @@ public abstract class JavaPlugin extends PluginBase {
|
|||||||
this.classLoader = classLoader;
|
this.classLoader = classLoader;
|
||||||
this.configFile = new File(dataFolder, "config.yml");
|
this.configFile = new File(dataFolder, "config.yml");
|
||||||
this.logger = new PluginLogger(this);
|
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);
|
throw new IllegalArgumentException(clazz + " does not extend " + JavaPlugin.class);
|
||||||
}
|
}
|
||||||
final ClassLoader cl = clazz.getClassLoader();
|
final ClassLoader cl = clazz.getClassLoader();
|
||||||
if (!(cl instanceof PluginClassLoader)) {
|
if (!(cl instanceof io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader configuredPluginClassLoader)) { // Paper
|
||||||
throw new IllegalArgumentException(clazz + " is not initialized by " + PluginClassLoader.class);
|
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) {
|
if (plugin == null) {
|
||||||
throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer");
|
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) {
|
public static JavaPlugin getProvidingPlugin(@NotNull Class<?> clazz) {
|
||||||
Preconditions.checkArgument(clazz != null, "Null class cannot have a plugin");
|
Preconditions.checkArgument(clazz != null, "Null class cannot have a plugin");
|
||||||
final ClassLoader cl = clazz.getClassLoader();
|
final ClassLoader cl = clazz.getClassLoader();
|
||||||
if (!(cl instanceof PluginClassLoader)) {
|
if (!(cl instanceof io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader configuredPluginClassLoader)) { // Paper
|
||||||
throw new IllegalArgumentException(clazz + " is not provided by " + PluginClassLoader.class);
|
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) {
|
if (plugin == null) {
|
||||||
throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer");
|
throw new IllegalStateException("Cannot get plugin for " + clazz + " from a static initializer");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import org.yaml.snakeyaml.error.YAMLException;
|
|||||||
/**
|
/**
|
||||||
* Represents a Java plugin loader, allowing plugins in the form of .jar
|
* 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 {
|
public final class JavaPluginLoader implements PluginLoader {
|
||||||
final Server server;
|
final Server server;
|
||||||
private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")};
|
private final Pattern[] fileFilters = new Pattern[]{Pattern.compile("\\.jar$")};
|
||||||
@@ -79,6 +80,7 @@ public final class JavaPluginLoader implements PluginLoader {
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Plugin loadPlugin(@NotNull final File file) throws InvalidPluginException {
|
public Plugin loadPlugin(@NotNull final File file) throws InvalidPluginException {
|
||||||
|
if (true) throw new UnsupportedOperationException(); // Paper
|
||||||
Preconditions.checkArgument(file != null, "File cannot be null");
|
Preconditions.checkArgument(file != null, "File cannot be null");
|
||||||
|
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
@@ -142,7 +144,7 @@ public final class JavaPluginLoader implements PluginLoader {
|
|||||||
|
|
||||||
final PluginClassLoader loader;
|
final PluginClassLoader loader;
|
||||||
try {
|
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) {
|
} catch (InvalidPluginException ex) {
|
||||||
throw ex;
|
throw ex;
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ import org.eclipse.aether.transport.http.HttpTransporterFactory;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
class LibraryLoader
|
// Paper start
|
||||||
|
@org.jetbrains.annotations.ApiStatus.Internal
|
||||||
|
public class LibraryLoader
|
||||||
|
// Paper end
|
||||||
{
|
{
|
||||||
|
|
||||||
private final Logger logger;
|
private final Logger logger;
|
||||||
@@ -55,6 +58,7 @@ class LibraryLoader
|
|||||||
this.repository = locator.getService( RepositorySystem.class );
|
this.repository = locator.getService( RepositorySystem.class );
|
||||||
this.session = MavenRepositorySystemUtils.newSession();
|
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.setChecksumPolicy( RepositoryPolicy.CHECKSUM_POLICY_FAIL );
|
||||||
session.setLocalRepositoryManager( repository.newLocalRepositoryManager( session, new LocalRepository( "libraries" ) ) );
|
session.setLocalRepositoryManager( repository.newLocalRepositoryManager( session, new LocalRepository( "libraries" ) ) );
|
||||||
session.setTransferListener( new AbstractTransferListener()
|
session.setTransferListener( new AbstractTransferListener()
|
||||||
@@ -84,7 +88,7 @@ class LibraryLoader
|
|||||||
}
|
}
|
||||||
logger.log( Level.INFO, "[{0}] Loading {1} libraries... please wait", new Object[]
|
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<>();
|
List<Dependency> dependencies = new ArrayList<>();
|
||||||
@@ -122,7 +126,7 @@ class LibraryLoader
|
|||||||
jarFiles.add( url );
|
jarFiles.add( url );
|
||||||
logger.log( Level.INFO, "[{0}] Loaded library {1}", new Object[]
|
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
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
/**
|
/**
|
||||||
* A ClassLoader for plugins, to allow shared classes across multiple plugins
|
* 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 JavaPluginLoader loader;
|
||||||
private final Map<String, Class<?>> classes = new ConcurrentHashMap<String, Class<?>>();
|
private final Map<String, Class<?>> classes = new ConcurrentHashMap<String, Class<?>>();
|
||||||
private final PluginDescriptionFile description;
|
private final PluginDescriptionFile description;
|
||||||
@@ -45,24 +46,32 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
private JavaPlugin pluginInit;
|
private JavaPlugin pluginInit;
|
||||||
private IllegalStateException pluginState;
|
private IllegalStateException pluginState;
|
||||||
private final Set<String> seenIllegalAccess = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
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 {
|
static {
|
||||||
ClassLoader.registerAsParallelCapable();
|
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);
|
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.description = description;
|
||||||
this.dataFolder = dataFolder;
|
this.dataFolder = dataFolder;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.jar = new JarFile(file);
|
this.jar = jarFile; // Paper - use JarFile provided by SpigotPluginProvider
|
||||||
this.manifest = jar.getManifest();
|
this.manifest = jar.getManifest();
|
||||||
this.url = file.toURI().toURL();
|
this.url = file.toURI().toURL();
|
||||||
this.libraryLoader = libraryLoader;
|
this.libraryLoader = libraryLoader;
|
||||||
|
|
||||||
|
// Paper start
|
||||||
|
this.dependencyContext = dependencyContext;
|
||||||
|
this.classLoaderGroup = io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage.instance().registerSpigotGroup(this);
|
||||||
|
// Paper end
|
||||||
|
|
||||||
Class<?> jarClass;
|
Class<?> jarClass;
|
||||||
try {
|
try {
|
||||||
jarClass = Class.forName(description.getMain(), true, this);
|
jarClass = Class.forName(description.getMain(), true, this);
|
||||||
@@ -107,6 +116,27 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
return findResources(name);
|
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
|
@Override
|
||||||
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||||||
return loadClass0(name, resolve, true, true);
|
return loadClass0(name, resolve, true, true);
|
||||||
@@ -132,26 +162,11 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
|
|
||||||
if (checkGlobal) {
|
if (checkGlobal) {
|
||||||
// This ignores the libraries of other plugins, unless they are transitive dependencies.
|
// 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 (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 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) {
|
// Paper - Totally delete the illegal access logic, we are never going to enforce it anyways here.
|
||||||
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()});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -180,7 +195,7 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
throw new ClassNotFoundException(name, ex);
|
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('.');
|
int dot = name.lastIndexOf('.');
|
||||||
if (dot != -1) {
|
if (dot != -1) {
|
||||||
@@ -210,8 +225,8 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
result = super.findClass(name);
|
result = super.findClass(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
loader.setClass(name, result);
|
|
||||||
classes.put(name, result);
|
classes.put(name, result);
|
||||||
|
this.setClass(name, result); // Paper
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -220,6 +235,12 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
try {
|
try {
|
||||||
|
// Paper start
|
||||||
|
Collection<Class<?>> classes = getClasses();
|
||||||
|
for (Class<?> clazz : classes) {
|
||||||
|
removeClass(clazz);
|
||||||
|
}
|
||||||
|
// Paper end
|
||||||
super.close();
|
super.close();
|
||||||
} finally {
|
} finally {
|
||||||
jar.close();
|
jar.close();
|
||||||
@@ -231,7 +252,7 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
return classes.values();
|
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 != null, "Initializing plugin cannot be null");
|
||||||
Preconditions.checkArgument(javaPlugin.getClass().getClassLoader() == this, "Cannot initialize plugin outside of this class loader");
|
Preconditions.checkArgument(javaPlugin.getClass().getClassLoader() == this, "Cannot initialize plugin outside of this class loader");
|
||||||
if (this.plugin != null || this.pluginInit != null) {
|
if (this.plugin != null || this.pluginInit != null) {
|
||||||
@@ -241,6 +262,38 @@ final class PluginClassLoader extends URLClassLoader {
|
|||||||
pluginState = new IllegalStateException("Initial initialization");
|
pluginState = new IllegalStateException("Initial initialization");
|
||||||
this.pluginInit = javaPlugin;
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,6 +32,12 @@ public class TestPlugin extends PluginBase {
|
|||||||
public PluginDescriptionFile getDescription() {
|
public PluginDescriptionFile getDescription() {
|
||||||
return new PluginDescriptionFile(pluginName, "1.0", "test.test");
|
return new PluginDescriptionFile(pluginName, "1.0", "test.test");
|
||||||
}
|
}
|
||||||
|
// Paper start
|
||||||
|
@Override
|
||||||
|
public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
|
||||||
|
return getDescription();
|
||||||
|
}
|
||||||
|
// Paper end
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public FileConfiguration getConfig() {
|
public FileConfiguration getConfig() {
|
||||||
|
|||||||
@@ -27,8 +27,7 @@ public final class TestServer {
|
|||||||
Thread creatingThread = Thread.currentThread();
|
Thread creatingThread = Thread.currentThread();
|
||||||
when(instance.isPrimaryThread()).then(mock -> Thread.currentThread().equals(creatingThread));
|
when(instance.isPrimaryThread()).then(mock -> Thread.currentThread().equals(creatingThread));
|
||||||
|
|
||||||
PluginManager pluginManager = new SimplePluginManager(instance, new SimpleCommandMap(instance));
|
// Paper - remove plugin manager for Paper Plugins
|
||||||
when(instance.getPluginManager()).thenReturn(pluginManager);
|
|
||||||
|
|
||||||
Logger logger = Logger.getLogger(TestServer.class.getCanonicalName());
|
Logger logger = Logger.getLogger(TestServer.class.getCanonicalName());
|
||||||
when(instance.getLogger()).thenReturn(logger);
|
when(instance.getLogger()).thenReturn(logger);
|
||||||
|
|||||||
Reference in New Issue
Block a user