Mirror von
https://github.com/PaperMC/Paper.git
synchronisiert 2024-11-15 12:30:06 +01:00
8c4e81184b
Also fixes logging errors
6818 Zeilen
276 KiB
Diff
6818 Zeilen
276 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
|
||
Date: Wed, 6 Jul 2022 23:00:31 -0400
|
||
Subject: [PATCH] Paper Plugins
|
||
|
||
|
||
diff --git a/src/main/java/io/papermc/paper/command/PaperCommand.java b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||
index b3a58bf4b654e336826dc04da9e2f80ff8b9a9a7..156334e3876d966fedc91d18da29f562395ab182 100644
|
||
--- a/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||
+++ b/src/main/java/io/papermc/paper/command/PaperCommand.java
|
||
@@ -4,6 +4,7 @@ import io.papermc.paper.command.subcommands.EntityCommand;
|
||
import io.papermc.paper.command.subcommands.HeapDumpCommand;
|
||
import io.papermc.paper.command.subcommands.ReloadCommand;
|
||
import io.papermc.paper.command.subcommands.VersionCommand;
|
||
+import io.papermc.paper.command.subcommands.DumpPluginsCommand;
|
||
import it.unimi.dsi.fastutil.Pair;
|
||
import java.util.ArrayList;
|
||
import java.util.Arrays;
|
||
@@ -40,6 +41,7 @@ public final class PaperCommand extends Command {
|
||
commands.put(Set.of("entity"), new EntityCommand());
|
||
commands.put(Set.of("reload"), new ReloadCommand());
|
||
commands.put(Set.of("version"), new VersionCommand());
|
||
+ commands.put(Set.of("dumpplugins"), new DumpPluginsCommand());
|
||
|
||
return commands.entrySet().stream()
|
||
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
|
||
diff --git a/src/main/java/io/papermc/paper/command/PaperCommands.java b/src/main/java/io/papermc/paper/command/PaperCommands.java
|
||
index 6a00f3d38da8107825ab1d405f337fd077b09f72..d44d0074446c1c54e87dc8078dff7fef1d92f343 100644
|
||
--- a/src/main/java/io/papermc/paper/command/PaperCommands.java
|
||
+++ b/src/main/java/io/papermc/paper/command/PaperCommands.java
|
||
@@ -23,5 +23,6 @@ public final class PaperCommands {
|
||
COMMANDS.forEach((s, command) -> {
|
||
server.server.getCommandMap().register(s, "Paper", command);
|
||
});
|
||
+ server.server.getCommandMap().register("bukkit", new PaperPluginsCommand());
|
||
}
|
||
}
|
||
diff --git a/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..72096a66a4046633de73a12f5a043ac6dff169b1
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/command/PaperPluginsCommand.java
|
||
@@ -0,0 +1,207 @@
|
||
+package io.papermc.paper.command;
|
||
+
|
||
+import com.google.common.collect.Lists;
|
||
+import io.leangen.geantyref.GenericTypeReflector;
|
||
+import io.leangen.geantyref.TypeToken;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||
+import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatus;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatusHolder;
|
||
+import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent;
|
||
+import io.papermc.paper.plugin.provider.type.spigot.SpigotPluginProvider;
|
||
+import net.kyori.adventure.text.Component;
|
||
+import net.kyori.adventure.text.JoinConfiguration;
|
||
+import net.kyori.adventure.text.TextComponent;
|
||
+import net.kyori.adventure.text.event.ClickEvent;
|
||
+import net.kyori.adventure.text.format.NamedTextColor;
|
||
+import net.kyori.adventure.text.format.TextColor;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.command.CommandSender;
|
||
+import org.bukkit.command.defaults.BukkitCommand;
|
||
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
||
+import org.bukkit.plugin.Plugin;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.lang.reflect.Type;
|
||
+import java.util.ArrayList;
|
||
+import java.util.Arrays;
|
||
+import java.util.Collections;
|
||
+import java.util.List;
|
||
+import java.util.TreeMap;
|
||
+
|
||
+public class PaperPluginsCommand extends BukkitCommand {
|
||
+
|
||
+ private static final TextColor INFO_COLOR = TextColor.color(52, 159, 218);
|
||
+
|
||
+ // TODO: LINK?
|
||
+ private static final Component SERVER_PLUGIN_INFO = Component.text("ℹ What is a server plugin?", INFO_COLOR)
|
||
+ .append(asPlainComponents("""
|
||
+ Server plugins can add new behavior to your server!
|
||
+ You can find new plugins on Paper's plugin repository, Hangar.
|
||
+
|
||
+ <link to hangar>
|
||
+ """));
|
||
+
|
||
+ private static final Component SERVER_INITIALIZER_INFO = Component.text("ℹ What is a server initializer?", INFO_COLOR)
|
||
+ .append(asPlainComponents("""
|
||
+ Server initializers are ran before your server
|
||
+ starts and are provided by paper plugins.
|
||
+ """));
|
||
+
|
||
+ private static final Component LEGACY_PLUGIN_INFO = Component.text("ℹ What is a legacy plugin?", INFO_COLOR)
|
||
+ .append(asPlainComponents("""
|
||
+ A legacy plugin is a plugin that was made on
|
||
+ very old unsupported versions of the game.
|
||
+
|
||
+ It is encouraged that you replace this plugin,
|
||
+ as they might not work in the future and may cause
|
||
+ performance issues.
|
||
+ """));
|
||
+
|
||
+ private static final Component LEGACY_PLUGIN_STAR = Component.text('*', TextColor.color(255, 212, 42)).hoverEvent(LEGACY_PLUGIN_INFO);
|
||
+ private static final Component INFO_ICON_START = Component.text("ℹ ", INFO_COLOR);
|
||
+ private static final Component PAPER_HEADER = Component.text("Paper Plugins:", TextColor.color(2, 136, 209));
|
||
+ private static final Component BUKKIT_HEADER = Component.text("Bukkit Plugins:", TextColor.color(237, 129, 6));
|
||
+ private static final Component PLUGIN_TICK = Component.text("- ", NamedTextColor.DARK_GRAY);
|
||
+ private static final Component PLUGIN_TICK_EMPTY = Component.text(" ");
|
||
+
|
||
+ private static final Type JAVA_PLUGIN_PROVIDER_TYPE = new TypeToken<PluginProvider<JavaPlugin>>() {}.getType();
|
||
+
|
||
+ public PaperPluginsCommand() {
|
||
+ super("plugins");
|
||
+ this.description = "Gets a list of plugins running on the server";
|
||
+ this.usageMessage = "/plugins";
|
||
+ this.setPermission("bukkit.command.plugins");
|
||
+ this.setAliases(Arrays.asList("pl"));
|
||
+ }
|
||
+
|
||
+ private static <T> List<Component> formatProviders(TreeMap<String, PluginProvider<T>> plugins) {
|
||
+ List<Component> components = new ArrayList<>(plugins.size());
|
||
+ for (PluginProvider<T> entry : plugins.values()) {
|
||
+ components.add(formatProvider(entry));
|
||
+ }
|
||
+
|
||
+ boolean isFirst = true;
|
||
+ List<Component> formattedSublists = new ArrayList<>();
|
||
+ /*
|
||
+ Split up the plugin list for each 10 plugins to get size down
|
||
+
|
||
+ Plugin List:
|
||
+ - Plugin 1, Plugin 2, .... Plugin 10,
|
||
+ Plugin 11, Plugin 12 ... Plugin 20,
|
||
+ */
|
||
+ for (List<Component> componentSublist : Lists.partition(components, 10)) {
|
||
+ Component component = Component.space();
|
||
+ if (isFirst) {
|
||
+ component = component.append(PLUGIN_TICK);
|
||
+ isFirst = false;
|
||
+ } else {
|
||
+ component = PLUGIN_TICK_EMPTY;
|
||
+ //formattedSublists.add(Component.empty()); // Add an empty line, the auto chat wrapping and this makes it quite jarring.
|
||
+ }
|
||
+
|
||
+ formattedSublists.add(component.append(Component.join(JoinConfiguration.commas(true), componentSublist)));
|
||
+ }
|
||
+
|
||
+ return formattedSublists;
|
||
+ }
|
||
+
|
||
+ private static Component formatProvider(PluginProvider<?> provider) {
|
||
+ TextComponent.Builder builder = Component.text();
|
||
+ if (provider instanceof SpigotPluginProvider spigotPluginProvider && CraftMagicNumbers.isLegacy(spigotPluginProvider.getMeta())) {
|
||
+ builder.append(LEGACY_PLUGIN_STAR);
|
||
+ }
|
||
+
|
||
+ String name = provider.getMeta().getName();
|
||
+ Component pluginName = Component.text(name, fromStatus(provider))
|
||
+ .clickEvent(ClickEvent.runCommand("/version " + name));
|
||
+
|
||
+ builder.append(pluginName);
|
||
+
|
||
+ return builder.build();
|
||
+ }
|
||
+
|
||
+ private static Component asPlainComponents(String strings) {
|
||
+ net.kyori.adventure.text.TextComponent.Builder builder = Component.text();
|
||
+ for (String string : strings.split("\n")) {
|
||
+ builder.append(Component.newline());
|
||
+ builder.append(Component.text(string, NamedTextColor.WHITE));
|
||
+ }
|
||
+
|
||
+ return builder.build();
|
||
+ }
|
||
+
|
||
+ private static TextColor fromStatus(PluginProvider<?> provider) {
|
||
+ if (provider instanceof ProviderStatusHolder statusHolder && statusHolder.getLastProvidedStatus() != null) {
|
||
+ ProviderStatus status = statusHolder.getLastProvidedStatus();
|
||
+
|
||
+ // Handle enabled/disabled game plugins
|
||
+ if (status == ProviderStatus.INITIALIZED && GenericTypeReflector.isSuperType(JAVA_PLUGIN_PROVIDER_TYPE, provider.getClass())) {
|
||
+ Plugin plugin = Bukkit.getPluginManager().getPlugin(provider.getMeta().getName());
|
||
+ // Plugin doesn't exist? Could be due to it being removed.
|
||
+ if (plugin == null) {
|
||
+ return NamedTextColor.RED;
|
||
+ }
|
||
+
|
||
+ return plugin.isEnabled() ? NamedTextColor.GREEN : NamedTextColor.RED;
|
||
+ }
|
||
+
|
||
+ return switch (status) {
|
||
+ case INITIALIZED -> NamedTextColor.GREEN;
|
||
+ case ERRORED -> NamedTextColor.RED;
|
||
+ };
|
||
+ } else if (provider instanceof PaperPluginParent.PaperServerPluginProvider serverPluginProvider && serverPluginProvider.shouldSkipCreation()) {
|
||
+ // Paper plugins will be skipped if their provider is skipped due to their initializer failing.
|
||
+ // Show them as red
|
||
+ return NamedTextColor.RED;
|
||
+ } else {
|
||
+ // Separated for future logic choice, but this indicated a provider that failed to load due to
|
||
+ // dependency issues or what not.
|
||
+ return NamedTextColor.RED;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean execute(@NotNull CommandSender sender, @NotNull String currentAlias, @NotNull String[] args) {
|
||
+ if (!this.testPermission(sender)) return true;
|
||
+
|
||
+ TreeMap<String, PluginProvider<JavaPlugin>> paperPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||
+ TreeMap<String, PluginProvider<JavaPlugin>> spigotPlugins = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
|
||
+
|
||
+
|
||
+ for (PluginProvider<JavaPlugin> provider : LaunchEntryPointHandler.INSTANCE.get(Entrypoint.PLUGIN).getRegisteredProviders()) {
|
||
+ PluginMeta configuration = provider.getMeta();
|
||
+
|
||
+ if (provider instanceof SpigotPluginProvider) {
|
||
+ spigotPlugins.put(configuration.getDisplayName(), provider);
|
||
+ } else if (provider instanceof PaperPluginParent.PaperServerPluginProvider) {
|
||
+ paperPlugins.put(configuration.getDisplayName(), provider);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ Component infoMessage = Component.text("Server Plugins (%s):".formatted(paperPlugins.size() + spigotPlugins.size()), NamedTextColor.WHITE);
|
||
+ //.append(INFO_ICON_START.hoverEvent(SERVER_PLUGIN_INFO)); TODO: Add docs
|
||
+
|
||
+ sender.sendMessage(infoMessage);
|
||
+ sender.sendMessage(PAPER_HEADER);
|
||
+ for (Component component : formatProviders(paperPlugins)) {
|
||
+ sender.sendMessage(component);
|
||
+ }
|
||
+ sender.sendMessage(BUKKIT_HEADER);
|
||
+ for (Component component : formatProviders(spigotPlugins)) {
|
||
+ sender.sendMessage(component);
|
||
+ }
|
||
+
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ @NotNull
|
||
+ @Override
|
||
+ public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
|
||
+ return Collections.emptyList();
|
||
+ }
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..254854646b748e5bb47657625315ced51b22887f
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java
|
||
@@ -0,0 +1,201 @@
|
||
+package io.papermc.paper.command.subcommands;
|
||
+
|
||
+import com.google.gson.JsonArray;
|
||
+import com.google.gson.JsonElement;
|
||
+import com.google.gson.JsonObject;
|
||
+import com.google.gson.JsonPrimitive;
|
||
+import com.google.gson.internal.Streams;
|
||
+import com.google.gson.stream.JsonWriter;
|
||
+import io.papermc.paper.command.PaperSubcommand;
|
||
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||
+import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.group.LockingClassLoaderGroup;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.group.SimpleListPluginClassLoaderGroup;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration;
|
||
+import io.papermc.paper.plugin.manager.PaperPluginManagerImpl;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage;
|
||
+import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup;
|
||
+import io.papermc.paper.plugin.storage.ConfiguredProviderStorage;
|
||
+import io.papermc.paper.plugin.storage.ProviderStorage;
|
||
+import net.minecraft.server.MinecraftServer;
|
||
+import org.bukkit.command.CommandSender;
|
||
+import org.bukkit.plugin.Plugin;
|
||
+import org.checkerframework.checker.nullness.qual.NonNull;
|
||
+import org.checkerframework.framework.qual.DefaultQualifier;
|
||
+
|
||
+import java.io.PrintStream;
|
||
+import java.io.StringWriter;
|
||
+import java.nio.charset.StandardCharsets;
|
||
+import java.nio.file.Files;
|
||
+import java.nio.file.Path;
|
||
+import java.time.LocalDateTime;
|
||
+import java.time.format.DateTimeFormatter;
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+
|
||
+import static net.kyori.adventure.text.Component.text;
|
||
+import static net.kyori.adventure.text.format.NamedTextColor.GREEN;
|
||
+import static net.kyori.adventure.text.format.NamedTextColor.RED;
|
||
+
|
||
+@DefaultQualifier(NonNull.class)
|
||
+public final class DumpPluginsCommand implements PaperSubcommand {
|
||
+ @Override
|
||
+ public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
|
||
+ this.dumpPlugins(sender, args);
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
|
||
+
|
||
+ private void dumpPlugins(final CommandSender sender, final String[] args) {
|
||
+ Path parent = Path.of("debug");
|
||
+ Path path = parent.resolve("plugin-info" + FORMATTER.format(LocalDateTime.now()) + ".txt");
|
||
+ try {
|
||
+ Files.createDirectories(parent);
|
||
+ Files.createFile(path);
|
||
+ sender.sendMessage(text("Writing plugin information to " + path, GREEN));
|
||
+
|
||
+ final JsonObject data = this.writeDebug();
|
||
+
|
||
+ StringWriter stringWriter = new StringWriter();
|
||
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||
+ jsonWriter.setIndent(" ");
|
||
+ jsonWriter.setLenient(false);
|
||
+ Streams.write(data, jsonWriter);
|
||
+
|
||
+ try (PrintStream out = new PrintStream(Files.newOutputStream(path), false, StandardCharsets.UTF_8)) {
|
||
+ out.print(stringWriter);
|
||
+ }
|
||
+ sender.sendMessage(text("Successfully written plugin debug information!", GREEN));
|
||
+ } catch (Throwable e) {
|
||
+ sender.sendMessage(text("Failed to write plugin information! See the console for more info.", RED));
|
||
+ MinecraftServer.LOGGER.warn("Error occurred while dumping plugin info", e);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private JsonObject writeDebug() {
|
||
+ JsonObject root = new JsonObject();
|
||
+ if (ConfiguredProviderStorage.LEGACY_PLUGIN_LOADING) {
|
||
+ root.addProperty("legacy-loading-strategy", true);
|
||
+ }
|
||
+
|
||
+ this.writeProviders(root);
|
||
+ this.writePlugins(root);
|
||
+ this.writeClassloaders(root);
|
||
+
|
||
+ return root;
|
||
+ }
|
||
+
|
||
+ private void writeProviders(JsonObject root) {
|
||
+ JsonObject rootProviders = new JsonObject();
|
||
+ root.add("providers", rootProviders);
|
||
+
|
||
+ for (Map.Entry<Entrypoint<?>, ProviderStorage<?>> entry : LaunchEntryPointHandler.INSTANCE.getStorage().entrySet()) {
|
||
+ JsonObject entrypoint = new JsonObject();
|
||
+
|
||
+ JsonArray providers = new JsonArray();
|
||
+ entrypoint.add("providers", providers);
|
||
+
|
||
+ List<PluginProvider<Object>> pluginProviders = new ArrayList<>();
|
||
+ for (PluginProvider<?> provider : entry.getValue().getRegisteredProviders()) {
|
||
+ JsonObject providerObj = new JsonObject();
|
||
+ providerObj.addProperty("name", provider.getMeta().getName());
|
||
+ providerObj.addProperty("version", provider.getMeta().getVersion());
|
||
+ providerObj.addProperty("dependencies", provider.getMeta().getPluginDependencies().toString());
|
||
+ providerObj.addProperty("soft-dependencies", provider.getMeta().getPluginSoftDependencies().toString());
|
||
+ providerObj.addProperty("load-before", provider.getMeta().getLoadBeforePlugins().toString());
|
||
+
|
||
+
|
||
+ providers.add(providerObj);
|
||
+ pluginProviders.add((PluginProvider<Object>) provider);
|
||
+ }
|
||
+
|
||
+ JsonArray loadOrder = new JsonArray();
|
||
+ entrypoint.add("load-order", loadOrder);
|
||
+
|
||
+ ModernPluginLoadingStrategy<Object> modernPluginLoadingStrategy = new ModernPluginLoadingStrategy<>(new ProviderConfiguration<>() {
|
||
+ @Override
|
||
+ public void applyContext(PluginProvider<Object> provider, DependencyContext dependencyContext) {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean load(PluginProvider<Object> provider, Object provided) {
|
||
+ loadOrder.add(provider.getMeta().getName());
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> requiredDependencies(PluginProvider<Object> provider) {
|
||
+ return provider.getMeta().getPluginDependencies();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> optionalDependencies(PluginProvider<Object> provider) {
|
||
+ return provider.getMeta().getPluginSoftDependencies();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> loadBeforeDependencies(PluginProvider<Object> provider) {
|
||
+ return provider.getMeta().getLoadBeforePlugins();
|
||
+ }
|
||
+ });
|
||
+ modernPluginLoadingStrategy.loadProviders(pluginProviders);
|
||
+
|
||
+ rootProviders.add(entry.getKey().getDebugName(), entrypoint);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private void writePlugins(JsonObject root) {
|
||
+ JsonArray rootPlugins = new JsonArray();
|
||
+ root.add("plugins", rootPlugins);
|
||
+
|
||
+ for (Plugin plugin : PaperPluginManagerImpl.getInstance().getPlugins()) {
|
||
+ rootPlugins.add(plugin.toString());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private void writeClassloaders(JsonObject root) {
|
||
+ JsonObject classLoadersRoot = new JsonObject();
|
||
+ root.add("classloaders", classLoadersRoot);
|
||
+
|
||
+ PaperPluginClassLoaderStorage storage = (PaperPluginClassLoaderStorage) PaperClassLoaderStorage.instance();
|
||
+ classLoadersRoot.addProperty("global", storage.getGlobalGroup().toString());
|
||
+ classLoadersRoot.addProperty("dependency_graph", PaperPluginManagerImpl.getInstance().getInstanceManagerGraph().toString());
|
||
+
|
||
+ JsonArray array = new JsonArray();
|
||
+ classLoadersRoot.add("children", array);
|
||
+ for (PluginClassLoaderGroup group : storage.getGroups()) {
|
||
+ array.add(this.writeClassloader(group));
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private JsonObject writeClassloader(PluginClassLoaderGroup group) {
|
||
+ JsonObject classLoadersRoot = new JsonObject();
|
||
+ if (group instanceof SimpleListPluginClassLoaderGroup listGroup) {
|
||
+ JsonArray array = new JsonArray();
|
||
+ classLoadersRoot.addProperty("main", listGroup.toString());
|
||
+ classLoadersRoot.add("children", array);
|
||
+ for (ConfiguredPluginClassLoader innerGroup : listGroup.getClassLoaders()) {
|
||
+ array.add(this.writeClassloader(innerGroup));
|
||
+ }
|
||
+
|
||
+ } else if (group instanceof LockingClassLoaderGroup locking) {
|
||
+ // Unwrap
|
||
+ return this.writeClassloader(locking.getParent());
|
||
+ } else {
|
||
+ classLoadersRoot.addProperty("raw", group.toString());
|
||
+ }
|
||
+
|
||
+ return classLoadersRoot;
|
||
+ }
|
||
+
|
||
+ private JsonElement writeClassloader(ConfiguredPluginClassLoader innerGroup) {
|
||
+ return new JsonPrimitive(innerGroup.toString());
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..85fa762d7d3476510886a7cc7b3bc79acfce4425
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/PluginInitializerManager.java
|
||
@@ -0,0 +1,78 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+import com.mojang.logging.LogUtils;
|
||
+import joptsimple.OptionSet;
|
||
+import org.bukkit.configuration.file.YamlConfiguration;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+import java.io.File;
|
||
+import java.io.IOException;
|
||
+import java.nio.file.Files;
|
||
+import java.nio.file.Path;
|
||
+
|
||
+public class PluginInitializerManager {
|
||
+
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+ private static PluginInitializerManager impl;
|
||
+ private final Path pluginDirectory;
|
||
+ private final Path updateDirectory;
|
||
+
|
||
+ PluginInitializerManager(@NotNull OptionSet minecraftOptionSet) {
|
||
+ // We have to load the bukkit configuration inorder to get the update folder location.
|
||
+ File configFileLocationBukkit = (File) minecraftOptionSet.valueOf("bukkit-settings");
|
||
+ this.pluginDirectory = ((File) minecraftOptionSet.valueOf("plugins")).toPath();
|
||
+
|
||
+ String updateDirectory = YamlConfiguration.loadConfiguration(configFileLocationBukkit).getString("settings.update-folder", "update");
|
||
+ if (updateDirectory.isBlank()) {
|
||
+ this.updateDirectory = null;
|
||
+ } else {
|
||
+ Path resolvedUpdateDirectory = this.pluginDirectory.resolve(updateDirectory);
|
||
+ if (!Files.isDirectory(resolvedUpdateDirectory)) {
|
||
+ this.updateDirectory = null;
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ boolean isSameFile = true;
|
||
+ try {
|
||
+ isSameFile = Files.isSameFile(resolvedUpdateDirectory, this.pluginDirectory);
|
||
+ } catch (IOException e) {
|
||
+ LOGGER.error("Misconfigured update directory!");
|
||
+ LOGGER.error("Failed to compare update/plugin directory", e);
|
||
+ }
|
||
+
|
||
+ if (isSameFile) {
|
||
+ LOGGER.error("Misconfigured update directory!");
|
||
+ LOGGER.error(("Your configured update directory (%s) in bukkit.yml is pointing to the same location as the plugin directory (%s). " +
|
||
+ "Disabling auto updating functionality.").formatted(resolvedUpdateDirectory, this.pluginDirectory));
|
||
+
|
||
+ this.updateDirectory = null;
|
||
+ } else {
|
||
+ this.updateDirectory = resolvedUpdateDirectory;
|
||
+ }
|
||
+
|
||
+ }
|
||
+
|
||
+ }
|
||
+
|
||
+ public static PluginInitializerManager init(OptionSet optionSet) {
|
||
+ impl = new PluginInitializerManager(optionSet);
|
||
+ return impl;
|
||
+ }
|
||
+
|
||
+ public static PluginInitializerManager instance() {
|
||
+ return impl;
|
||
+ }
|
||
+
|
||
+ @NotNull
|
||
+ public Path pluginDirectoryPath() {
|
||
+ return pluginDirectory;
|
||
+ }
|
||
+
|
||
+ @Nullable
|
||
+ public Path pluginUpdatePath() {
|
||
+ return updateDirectory;
|
||
+ }
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContextImpl.java b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContextImpl.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..8249a7024537fccd99735b92abb1368e6647b5ae
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/bootstrap/PluginProviderContextImpl.java
|
||
@@ -0,0 +1,40 @@
|
||
+package io.papermc.paper.plugin.bootstrap;
|
||
+
|
||
+import io.papermc.paper.plugin.PluginInitializerManager;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.nio.file.Path;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+public record PluginProviderContextImpl(PluginMeta config, Path dataFolder,
|
||
+ Logger logger) implements PluginProviderContext {
|
||
+
|
||
+ public static PluginProviderContextImpl of(PluginMeta config, Logger logger) {
|
||
+ Path dataFolder = PluginInitializerManager.instance().pluginDirectoryPath().resolve(config.getDisplayName());
|
||
+
|
||
+ return new PluginProviderContextImpl(config, dataFolder, logger);
|
||
+ }
|
||
+
|
||
+ public static PluginProviderContextImpl of(PluginProvider<?> provider, Path pluginFolder) {
|
||
+ Path dataFolder = pluginFolder.resolve(provider.getMeta().getDisplayName());
|
||
+
|
||
+ return new PluginProviderContextImpl(provider.getMeta(), dataFolder, provider.getLogger());
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull PluginMeta getConfiguration() {
|
||
+ return this.config;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Path getDataDirectory() {
|
||
+ return this.dataFolder;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Logger getLogger() {
|
||
+ return this.logger;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/Entrypoint.java b/src/main/java/io/papermc/paper/plugin/entrypoint/Entrypoint.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..125008ac7db8b9f3fb57c49f8e4facc4ad4bb136
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/Entrypoint.java
|
||
@@ -0,0 +1,25 @@
|
||
+package io.papermc.paper.plugin.entrypoint;
|
||
+
|
||
+import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+
|
||
+/**
|
||
+ * Used to mark a certain place that {@link EntrypointHandler} will register {@link io.papermc.paper.plugin.provider.PluginProvider} under.
|
||
+ * Used for loading only certain providers at a certain time.
|
||
+ * @param <T> provider type
|
||
+ */
|
||
+public final class Entrypoint<T> {
|
||
+
|
||
+ public static final Entrypoint<PluginBootstrap> BOOTSTRAPPER = new Entrypoint<>("bootstrapper");
|
||
+ public static final Entrypoint<JavaPlugin> PLUGIN = new Entrypoint<>("plugin");
|
||
+
|
||
+ private final String debugName;
|
||
+
|
||
+ private Entrypoint(String debugName) {
|
||
+ this.debugName = debugName;
|
||
+ }
|
||
+
|
||
+ public String getDebugName() {
|
||
+ return debugName;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/EntrypointHandler.java b/src/main/java/io/papermc/paper/plugin/entrypoint/EntrypointHandler.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..b38e1e0f3d3055086f51bb191fd4b60ecf32d016
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/EntrypointHandler.java
|
||
@@ -0,0 +1,14 @@
|
||
+package io.papermc.paper.plugin.entrypoint;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+
|
||
+/**
|
||
+ * Represents a register that will register providers at a certain {@link Entrypoint},
|
||
+ * where then when the given {@link Entrypoint} is registered those will be loaded.
|
||
+ */
|
||
+public interface EntrypointHandler {
|
||
+
|
||
+ <T> void register(Entrypoint<T> entrypoint, PluginProvider<T> provider);
|
||
+
|
||
+ void enter(Entrypoint<?> entrypoint);
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..61a67971a41527c0e3b614bf48d2bc8eabd443b5
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/LaunchEntryPointHandler.java
|
||
@@ -0,0 +1,60 @@
|
||
+package io.papermc.paper.plugin.entrypoint;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.storage.BootstrapProviderStorage;
|
||
+import io.papermc.paper.plugin.storage.ProviderStorage;
|
||
+import io.papermc.paper.plugin.storage.ServerPluginProviderStorage;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+import java.util.HashMap;
|
||
+import java.util.Map;
|
||
+
|
||
+/**
|
||
+ * Used by the server to register/load plugin bootstrappers and plugins.
|
||
+ */
|
||
+public class LaunchEntryPointHandler implements EntrypointHandler {
|
||
+
|
||
+ public static final LaunchEntryPointHandler INSTANCE = new LaunchEntryPointHandler();
|
||
+ private final Map<Entrypoint<?>, ProviderStorage<?>> storage = new HashMap<>();
|
||
+
|
||
+ LaunchEntryPointHandler() {
|
||
+ this.storage.put(Entrypoint.BOOTSTRAPPER, new BootstrapProviderStorage());
|
||
+ this.storage.put(Entrypoint.PLUGIN, new ServerPluginProviderStorage());
|
||
+ }
|
||
+
|
||
+ // Utility
|
||
+ public static void enterBootstrappers() {
|
||
+ LaunchEntryPointHandler.INSTANCE.enter(Entrypoint.BOOTSTRAPPER);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void enter(Entrypoint<?> entrypoint) {
|
||
+ ProviderStorage<?> storage = this.storage.get(entrypoint);
|
||
+ if (storage == null) {
|
||
+ throw new IllegalArgumentException("No storage registered for entrypoint %s.".formatted(entrypoint));
|
||
+ }
|
||
+
|
||
+ storage.enter();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public <T> void register(Entrypoint<T> entrypoint, PluginProvider<T> provider) {
|
||
+ ProviderStorage<T> storage = this.get(entrypoint);
|
||
+ if (storage == null) {
|
||
+ throw new IllegalArgumentException("No storage registered for entrypoint %s.".formatted(entrypoint));
|
||
+ }
|
||
+
|
||
+ storage.register(provider);
|
||
+ }
|
||
+
|
||
+ @SuppressWarnings("unchecked")
|
||
+ public <T> ProviderStorage<T> get(Entrypoint<T> entrypoint) {
|
||
+ return (ProviderStorage<T>) this.storage.get(entrypoint);
|
||
+ }
|
||
+
|
||
+ // Debug only
|
||
+ @ApiStatus.Internal
|
||
+ public Map<Entrypoint<?>, ProviderStorage<?>> getStorage() {
|
||
+ return storage;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/ClassloaderBytecodeModifier.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/ClassloaderBytecodeModifier.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..93b5196a960f3efbe0d28f5527ea2752426213ce
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/ClassloaderBytecodeModifier.java
|
||
@@ -0,0 +1,22 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import net.kyori.adventure.util.Services;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public interface ClassloaderBytecodeModifier {
|
||
+
|
||
+ static ClassloaderBytecodeModifier bytecodeModifier() {
|
||
+ return Provider.INSTANCE;
|
||
+ }
|
||
+
|
||
+ byte[] modify(PluginMeta config, byte[] bytecode);
|
||
+
|
||
+ class Provider {
|
||
+
|
||
+ private static final ClassloaderBytecodeModifier INSTANCE = Services.service(ClassloaderBytecodeModifier.class).orElseThrow();
|
||
+
|
||
+ }
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..f9a2c55a354c877749db3f92956de802ae575788
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperClassloaderBytecodeModifier.java
|
||
@@ -0,0 +1,12 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+
|
||
+// Stub, implement in future.
|
||
+public class PaperClassloaderBytecodeModifier implements ClassloaderBytecodeModifier {
|
||
+
|
||
+ @Override
|
||
+ public byte[] modify(PluginMeta configuration, byte[] bytecode) {
|
||
+ return bytecode;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..f1a2d25079c841daff19f41f2b24fb8e97d2c7d3
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperPluginClassLoader.java
|
||
@@ -0,0 +1,193 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage;
|
||
+import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage;
|
||
+import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup;
|
||
+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.plugin.PluginDescriptionFile;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.io.File;
|
||
+import java.io.IOException;
|
||
+import java.net.URL;
|
||
+import java.nio.file.Path;
|
||
+import java.util.ArrayList;
|
||
+import java.util.Collections;
|
||
+import java.util.Enumeration;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+import java.util.Set;
|
||
+import java.util.concurrent.ConcurrentHashMap;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+/**
|
||
+ * This is similar to a {@link org.bukkit.plugin.java.PluginClassLoader} but is completely kept hidden from the api.
|
||
+ * This is only used with Paper plugins.
|
||
+ *
|
||
+ * @see PaperPluginClassLoaderStorage
|
||
+ */
|
||
+public class PaperPluginClassLoader extends PaperSimplePluginClassLoader implements ConfiguredPluginClassLoader {
|
||
+
|
||
+ static {
|
||
+ registerAsParallelCapable();
|
||
+ }
|
||
+
|
||
+ private final ClassLoader libraryLoader;
|
||
+ private final Set<String> seenIllegalAccess = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||
+ private final Logger logger;
|
||
+ @Nullable
|
||
+ private JavaPlugin loadedJavaPlugin;
|
||
+ @Nullable
|
||
+ private PluginClassLoaderGroup group;
|
||
+
|
||
+ public PaperPluginClassLoader(Logger logger, Path source, JarFile file, PaperPluginMeta configuration, ClassLoader parentLoader, ClassLoader libraryLoader) throws IOException {
|
||
+ super(source, file, configuration, parentLoader);
|
||
+ this.libraryLoader = libraryLoader;
|
||
+
|
||
+ this.logger = logger;
|
||
+ if (this.configuration.hasOpenClassloader()) {
|
||
+ this.group = PaperClassLoaderStorage.instance().registerOpenGroup(this);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public void refreshClassloaderDependencyTree(DependencyContext dependencyContext) {
|
||
+ if (this.configuration.hasOpenClassloader()) {
|
||
+ return;
|
||
+ }
|
||
+ if (this.group != null) {
|
||
+ // We need to unregister the classloader inorder to allow for dependencies
|
||
+ // to be recalculated
|
||
+ PaperClassLoaderStorage.instance().unregisterClassloader(this);
|
||
+ }
|
||
+
|
||
+ this.group = PaperClassLoaderStorage.instance().registerAccessBackedGroup(this, (classLoader) -> {
|
||
+ return dependencyContext.isTransitiveDependency(PaperPluginClassLoader.this.configuration, classLoader.getConfiguration());
|
||
+ });
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public URL getResource(String name) {
|
||
+ URL resource = findResource(name);
|
||
+ if (resource == null && this.libraryLoader != null) {
|
||
+ return this.libraryLoader.getResource(name);
|
||
+ }
|
||
+ return resource;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Enumeration<URL> getResources(String name) throws IOException {
|
||
+ List<URL> resources = new ArrayList<>();
|
||
+ this.addEnumeration(resources, this.findResources(name));
|
||
+ if (this.libraryLoader != null) {
|
||
+ addEnumeration(resources, this.libraryLoader.getResources(name));
|
||
+ }
|
||
+ return Collections.enumeration(resources);
|
||
+ }
|
||
+
|
||
+ private <T> void addEnumeration(List<T> list, Enumeration<T> enumeration) {
|
||
+ while (enumeration.hasMoreElements()) {
|
||
+ list.add(enumeration.nextElement());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
|
||
+ return this.loadClass(name, resolve, true, true);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginMeta getConfiguration() {
|
||
+ return this.configuration;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Class<?> loadClass(@NotNull String name, boolean resolve, boolean checkGroup, boolean checkLibraries) throws ClassNotFoundException {
|
||
+ try {
|
||
+ Class<?> result = super.loadClass(name, resolve);
|
||
+
|
||
+ // SPIGOT-6749: Library classes will appear in the above, but we don't want to return them to other plugins
|
||
+ if (checkGroup || result.getClassLoader() == this) {
|
||
+ return result;
|
||
+ }
|
||
+ } catch (ClassNotFoundException ignored) {
|
||
+ }
|
||
+
|
||
+ if (checkLibraries) {
|
||
+ try {
|
||
+ return this.libraryLoader.loadClass(name);
|
||
+ } catch (ClassNotFoundException ignored) {
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (checkGroup) {
|
||
+ // This ignores the libraries of other plugins, unless they are transitive dependencies.
|
||
+ if (this.group == null) {
|
||
+ throw new IllegalStateException("Tried to resolve class while group was not yet initialized");
|
||
+ }
|
||
+
|
||
+ Class<?> clazz = this.group.getClassByName(name, resolve, this);
|
||
+ if (clazz != null) {
|
||
+ return clazz;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ throw new ClassNotFoundException(name);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void init(JavaPlugin plugin) {
|
||
+ PluginMeta config = this.configuration;
|
||
+ PluginDescriptionFile pluginDescriptionFile = new PluginDescriptionFile(
|
||
+ config.getName(),
|
||
+ config.getDisplayName(),
|
||
+ config.getProvidedPlugins(),
|
||
+ config.getMainClass(),
|
||
+ "", // Classloader load order api
|
||
+ List.of(), // Dependencies
|
||
+ List.of(), // Soft Depends
|
||
+ List.of(), // Load Before
|
||
+ config.getVersion(),
|
||
+ Map.of(), // Commands, we use a separate system
|
||
+ config.getDescription(),
|
||
+ config.getAuthors(),
|
||
+ config.getContributors(),
|
||
+ config.getWebsite(),
|
||
+ config.getLoggerPrefix(),
|
||
+ config.getLoadOrder(),
|
||
+ config.getPermissions(),
|
||
+ config.getPermissionDefault(),
|
||
+ Set.of(), // Aware api
|
||
+ config.getAPIVersion(),
|
||
+ List.of() // Libraries
|
||
+ );
|
||
+
|
||
+ File dataFolder = new File(Bukkit.getPluginsFolder(), pluginDescriptionFile.getName());
|
||
+
|
||
+ plugin.init(Bukkit.getServer(), pluginDescriptionFile, dataFolder, this.source.toFile(), this, config);
|
||
+ plugin.logger = this.logger;
|
||
+
|
||
+ this.loadedJavaPlugin = plugin;
|
||
+ }
|
||
+
|
||
+ @Nullable
|
||
+ public JavaPlugin getLoadedJavaPlugin() {
|
||
+ return this.loadedJavaPlugin;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "PaperPluginClassLoader{" +
|
||
+ "libraryLoader=" + this.libraryLoader +
|
||
+ ", seenIllegalAccess=" + this.seenIllegalAccess +
|
||
+ ", loadedJavaPlugin=" + this.loadedJavaPlugin +
|
||
+ ", group=" + this.group +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperSimplePluginClassLoader.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperSimplePluginClassLoader.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..6688f2af4a32c2f79785f162e2eac0330d439ac1
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/PaperSimplePluginClassLoader.java
|
||
@@ -0,0 +1,116 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||
+import io.papermc.paper.plugin.util.NamespaceChecker;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+import java.io.IOException;
|
||
+import java.io.InputStream;
|
||
+import java.net.URL;
|
||
+import java.net.URLClassLoader;
|
||
+import java.nio.file.Path;
|
||
+import java.security.CodeSigner;
|
||
+import java.security.CodeSource;
|
||
+import java.util.Enumeration;
|
||
+import java.util.jar.JarEntry;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.jar.Manifest;
|
||
+
|
||
+/**
|
||
+ * Represents a simple classloader used for paper plugin bootstrappers.
|
||
+ */
|
||
+@ApiStatus.Internal
|
||
+public class PaperSimplePluginClassLoader extends URLClassLoader {
|
||
+
|
||
+ static {
|
||
+ ClassLoader.registerAsParallelCapable();
|
||
+ }
|
||
+
|
||
+ protected final PaperPluginMeta configuration;
|
||
+ protected final Path source;
|
||
+ protected final Manifest jarManifest;
|
||
+ protected final URL jarUrl;
|
||
+ protected final JarFile jar;
|
||
+
|
||
+ public PaperSimplePluginClassLoader(Path source, JarFile file, PaperPluginMeta configuration, ClassLoader parentLoader) throws IOException {
|
||
+ super(source.getFileName().toString(), new URL[]{source.toUri().toURL()}, parentLoader);
|
||
+
|
||
+ this.source = source;
|
||
+ this.jarManifest = file.getManifest();
|
||
+ this.jarUrl = source.toUri().toURL();
|
||
+ this.configuration = configuration;
|
||
+ this.jar = file;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public URL getResource(String name) {
|
||
+ return this.findResource(name);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Enumeration<URL> getResources(String name) throws IOException {
|
||
+ return this.findResources(name);
|
||
+ }
|
||
+
|
||
+ // Bytecode modification supported loader
|
||
+ @Override
|
||
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||
+ NamespaceChecker.validateNameSpaceForClassloading(name);
|
||
+
|
||
+ // See UrlClassLoader#findClass(String)
|
||
+ String path = name.replace('.', '/').concat(".class");
|
||
+ JarEntry entry = this.jar.getJarEntry(path);
|
||
+ if (entry == null) {
|
||
+ throw new ClassNotFoundException();
|
||
+ }
|
||
+
|
||
+ // See URLClassLoader#defineClass(String, Resource)
|
||
+ byte[] classBytes;
|
||
+
|
||
+ try (InputStream is = this.jar.getInputStream(entry)) {
|
||
+ classBytes = is.readAllBytes();
|
||
+ } catch (IOException ex) {
|
||
+ throw new ClassNotFoundException(name, ex);
|
||
+ }
|
||
+
|
||
+ classBytes = ClassloaderBytecodeModifier.bytecodeModifier().modify(this.configuration, classBytes);
|
||
+
|
||
+ int dot = name.lastIndexOf('.');
|
||
+ if (dot != -1) {
|
||
+ String pkgName = name.substring(0, dot);
|
||
+ // Get defined package does not correctly handle sealed packages.
|
||
+ if (this.getDefinedPackage(pkgName) == null) {
|
||
+ try {
|
||
+ if (this.jarManifest != null) {
|
||
+ this.definePackage(pkgName, this.jarManifest, this.jarUrl);
|
||
+ } else {
|
||
+ this.definePackage(pkgName, null, null, null, null, null, null, null);
|
||
+ }
|
||
+ } catch (IllegalArgumentException ex) {
|
||
+ // parallel-capable class loaders: re-verify in case of a
|
||
+ // race condition
|
||
+ if (this.getDefinedPackage(pkgName) == null) {
|
||
+ // Should never happen
|
||
+ throw new IllegalStateException("Cannot find package " + pkgName);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ CodeSigner[] signers = entry.getCodeSigners();
|
||
+ CodeSource source = new CodeSource(this.jarUrl, signers);
|
||
+
|
||
+ return this.defineClass(name, classBytes, 0, classBytes.length, source);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "PaperSimplePluginClassLoader{" +
|
||
+ "configuration=" + this.configuration +
|
||
+ ", source=" + this.source +
|
||
+ ", jarManifest=" + this.jarManifest +
|
||
+ ", jarUrl=" + this.jarUrl +
|
||
+ ", jar=" + this.jar +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/DependencyBasedPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/DependencyBasedPluginClassLoaderGroup.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..eaf5c794cbe8d6138c9d60eaae20f5fc7711f541
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/DependencyBasedPluginClassLoaderGroup.java
|
||
@@ -0,0 +1,47 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public class DependencyBasedPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup {
|
||
+
|
||
+ private final GlobalPluginClassLoaderGroup globalPluginClassLoaderGroup;
|
||
+ private final ClassLoaderAccess access;
|
||
+
|
||
+ public DependencyBasedPluginClassLoaderGroup(GlobalPluginClassLoaderGroup globalPluginClassLoaderGroup, ClassLoaderAccess access) {
|
||
+ super(new ArrayList<>());
|
||
+ this.access = access;
|
||
+ this.globalPluginClassLoaderGroup = globalPluginClassLoaderGroup;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * This will refresh the dependencies of the current classloader.
|
||
+ */
|
||
+ public void populateDependencies() {
|
||
+ this.classloaders.clear();
|
||
+ for (ConfiguredPluginClassLoader configuredPluginClassLoader : this.globalPluginClassLoaderGroup.getClassLoaders()) {
|
||
+ if (this.access.canAccess(configuredPluginClassLoader)) {
|
||
+ this.classloaders.add(configuredPluginClassLoader);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ClassLoaderAccess getAccess() {
|
||
+ return this.access;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "DependencyBasedPluginClassLoaderGroup{" +
|
||
+ "globalPluginClassLoaderGroup=" + this.globalPluginClassLoaderGroup +
|
||
+ ", access=" + this.access +
|
||
+ ", classloaders=" + this.classloaders +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/GlobalPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/GlobalPluginClassLoaderGroup.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..2b7eef787f83e5a32896cb30c215406b6f652786
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/GlobalPluginClassLoaderGroup.java
|
||
@@ -0,0 +1,18 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public class GlobalPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup {
|
||
+
|
||
+ @Override
|
||
+ public ClassLoaderAccess getAccess() {
|
||
+ return (v) -> true;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return super.toString();
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/LockingClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/LockingClassLoaderGroup.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..aae50ebba6ba1579b75af5370c8b020d2a927b2c
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/LockingClassLoaderGroup.java
|
||
@@ -0,0 +1,76 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.util.HashMap;
|
||
+import java.util.Map;
|
||
+import java.util.concurrent.atomic.AtomicInteger;
|
||
+import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public class LockingClassLoaderGroup implements PluginClassLoaderGroup {
|
||
+
|
||
+ private final PluginClassLoaderGroup parent;
|
||
+ private final Map<String, ClassLockEntry> classLoadLock = new HashMap<>();
|
||
+
|
||
+ public LockingClassLoaderGroup(PluginClassLoaderGroup parent) {
|
||
+ this.parent = parent;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable Class<?> getClassByName(String name, boolean resolve, ConfiguredPluginClassLoader requester) {
|
||
+ // make MT safe
|
||
+ ClassLockEntry lock;
|
||
+ synchronized (this.classLoadLock) {
|
||
+ lock = this.classLoadLock.computeIfAbsent(name, (x) -> new ClassLockEntry(new AtomicInteger(0), new java.util.concurrent.locks.ReentrantReadWriteLock()));
|
||
+ lock.count.incrementAndGet();
|
||
+ }
|
||
+ lock.reentrantReadWriteLock.writeLock().lock();
|
||
+ try {
|
||
+ return parent.getClassByName(name, resolve, requester);
|
||
+ } finally {
|
||
+ synchronized (this.classLoadLock) {
|
||
+ lock.reentrantReadWriteLock.writeLock().unlock();
|
||
+ if (lock.count.get() == 1) {
|
||
+ this.classLoadLock.remove(name);
|
||
+ } else {
|
||
+ lock.count.decrementAndGet();
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ this.parent.remove(configuredPluginClassLoader);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ this.parent.add(configuredPluginClassLoader);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ClassLoaderAccess getAccess() {
|
||
+ return this.parent.getAccess();
|
||
+ }
|
||
+
|
||
+ public PluginClassLoaderGroup getParent() {
|
||
+ return parent;
|
||
+ }
|
||
+
|
||
+ record ClassLockEntry(AtomicInteger count, ReentrantReadWriteLock reentrantReadWriteLock) {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "LockingClassLoaderGroup{" +
|
||
+ "parent=" + this.parent +
|
||
+ ", classLoadLock=" + this.classLoadLock +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/PaperPluginClassLoaderStorage.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/PaperPluginClassLoaderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..2c906e2c7d972b221a41acd614e00d0fbc1227c6
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/PaperPluginClassLoaderStorage.java
|
||
@@ -0,0 +1,93 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage;
|
||
+import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.plugin.java.PluginClassLoader;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+import java.util.concurrent.CopyOnWriteArrayList;
|
||
+
|
||
+/**
|
||
+ * This is used for connecting multiple classloaders.
|
||
+ */
|
||
+public final class PaperPluginClassLoaderStorage implements PaperClassLoaderStorage {
|
||
+
|
||
+ private final GlobalPluginClassLoaderGroup globalGroup = new GlobalPluginClassLoaderGroup();
|
||
+ private final List<PluginClassLoaderGroup> groups = new CopyOnWriteArrayList<>();
|
||
+
|
||
+ public PaperPluginClassLoaderStorage() {
|
||
+ this.groups.add(this.globalGroup);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginClassLoaderGroup registerSpigotGroup(PluginClassLoader pluginClassLoader) {
|
||
+ return this.registerGroup(pluginClassLoader, new SpigotPluginClassLoaderGroup(this.globalGroup, (library) -> {
|
||
+ return Bukkit.getServer().getPluginManager().isTransitiveDependency(pluginClassLoader.getConfiguration(), library.getConfiguration());
|
||
+ }));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginClassLoaderGroup registerOpenGroup(ConfiguredPluginClassLoader classLoader) {
|
||
+ return this.registerGroup(classLoader, this.globalGroup);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginClassLoaderGroup registerAccessBackedGroup(ConfiguredPluginClassLoader classLoader, ClassLoaderAccess access) {
|
||
+ List<ConfiguredPluginClassLoader> allowedLoaders = new ArrayList<>();
|
||
+ for (ConfiguredPluginClassLoader configuredPluginClassLoader : this.globalGroup.getClassLoaders()) {
|
||
+ if (access.canAccess(configuredPluginClassLoader)) {
|
||
+ allowedLoaders.add(configuredPluginClassLoader);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return this.registerGroup(classLoader, new StaticPluginClassLoaderGroup(allowedLoaders, access));
|
||
+ }
|
||
+
|
||
+ private PluginClassLoaderGroup registerGroup(ConfiguredPluginClassLoader classLoader, PluginClassLoaderGroup group) {
|
||
+ // Now add this classloader to any groups that allows it (includes global)
|
||
+ for (PluginClassLoaderGroup loaderGroup : this.groups) {
|
||
+ if (loaderGroup.getAccess().canAccess(classLoader)) {
|
||
+ loaderGroup.add(classLoader);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ group = new LockingClassLoaderGroup(group);
|
||
+ this.groups.add(group);
|
||
+ return group;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void unregisterClassloader(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ this.globalGroup.remove(configuredPluginClassLoader);
|
||
+ for (PluginClassLoaderGroup group : this.groups) {
|
||
+ group.remove(configuredPluginClassLoader);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean registerUnsafePlugin(ConfiguredPluginClassLoader pluginLoader) {
|
||
+ if (this.globalGroup.getClassLoaders().contains(pluginLoader)) {
|
||
+ return false;
|
||
+ } else {
|
||
+ this.globalGroup.getClassLoaders().add(pluginLoader);
|
||
+ return true;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Debug only
|
||
+ @ApiStatus.Internal
|
||
+ public GlobalPluginClassLoaderGroup getGlobalGroup() {
|
||
+ return this.globalGroup;
|
||
+ }
|
||
+
|
||
+ // Debug only
|
||
+ @ApiStatus.Internal
|
||
+ public List<PluginClassLoaderGroup> getGroups() {
|
||
+ return this.groups;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SimpleListPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SimpleListPluginClassLoaderGroup.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..23b6cb297f46c9c2b2944a3ab4031c31414620ad
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SimpleListPluginClassLoaderGroup.java
|
||
@@ -0,0 +1,69 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.util.List;
|
||
+import java.util.concurrent.CopyOnWriteArrayList;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public abstract class SimpleListPluginClassLoaderGroup implements PluginClassLoaderGroup {
|
||
+
|
||
+ private static final boolean DISABLE_CLASS_PRIORITIZATION = Boolean.getBoolean("Paper.DisableClassPrioritization");
|
||
+
|
||
+ protected final List<ConfiguredPluginClassLoader> classloaders;
|
||
+
|
||
+ protected SimpleListPluginClassLoaderGroup() {
|
||
+ this(new CopyOnWriteArrayList<>());
|
||
+ }
|
||
+
|
||
+ protected SimpleListPluginClassLoaderGroup(List<ConfiguredPluginClassLoader> classloaders) {
|
||
+ this.classloaders = classloaders;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable Class<?> getClassByName(String name, boolean resolve, ConfiguredPluginClassLoader requester) {
|
||
+ if (!DISABLE_CLASS_PRIORITIZATION) {
|
||
+ try {
|
||
+ return this.lookupClass(name, false, requester); // First check the requester
|
||
+ } catch (ClassNotFoundException ignored) {
|
||
+ }
|
||
+ }
|
||
+
|
||
+ for (ConfiguredPluginClassLoader loader : this.classloaders) {
|
||
+ try {
|
||
+ return this.lookupClass(name, resolve, loader);
|
||
+ } catch (ClassNotFoundException ignored) {
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return null;
|
||
+ }
|
||
+
|
||
+ protected Class<?> lookupClass(String name, boolean resolve, ConfiguredPluginClassLoader current) throws ClassNotFoundException {
|
||
+ return current.loadClass(name, resolve, false, true);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ this.classloaders.remove(configuredPluginClassLoader);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ this.classloaders.add(configuredPluginClassLoader);
|
||
+ }
|
||
+
|
||
+ public List<ConfiguredPluginClassLoader> getClassLoaders() {
|
||
+ return classloaders;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "SimpleListPluginClassLoaderGroup{" +
|
||
+ "classloaders=" + this.classloaders +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SingletonPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SingletonPluginClassLoaderGroup.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..3b670bd6b35ae7f56488a9b50df54709a0b28901
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SingletonPluginClassLoaderGroup.java
|
||
@@ -0,0 +1,60 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.classloader.PluginClassLoaderGroup;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public class SingletonPluginClassLoaderGroup implements PluginClassLoaderGroup {
|
||
+
|
||
+ private final ConfiguredPluginClassLoader configuredPluginClassLoader;
|
||
+ private final Access access;
|
||
+
|
||
+ public SingletonPluginClassLoaderGroup(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ this.configuredPluginClassLoader = configuredPluginClassLoader;
|
||
+ this.access = new Access();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable Class<?> getClassByName(String name, boolean resolve, ConfiguredPluginClassLoader requester) {
|
||
+ try {
|
||
+ return this.configuredPluginClassLoader.loadClass(name, resolve, false, true);
|
||
+ } catch (ClassNotFoundException ignored) {
|
||
+ }
|
||
+
|
||
+ return null;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ClassLoaderAccess getAccess() {
|
||
+ return this.access;
|
||
+ }
|
||
+
|
||
+ @ApiStatus.Internal
|
||
+ private class Access implements ClassLoaderAccess {
|
||
+
|
||
+ @Override
|
||
+ public boolean canAccess(ConfiguredPluginClassLoader classLoader) {
|
||
+ return SingletonPluginClassLoaderGroup.this.configuredPluginClassLoader == classLoader;
|
||
+ }
|
||
+
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "SingletonPluginClassLoaderGroup{" +
|
||
+ "configuredPluginClassLoader=" + this.configuredPluginClassLoader +
|
||
+ ", access=" + this.access +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SpigotPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SpigotPluginClassLoaderGroup.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..5d26367524389388be163ae3120c1d2bf55cfef7
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/SpigotPluginClassLoaderGroup.java
|
||
@@ -0,0 +1,49 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+import java.util.function.Predicate;
|
||
+
|
||
+/**
|
||
+ * Spigot classloaders have the ability to see everything.
|
||
+ * However, libraries are ONLY shared depending on their dependencies.
|
||
+ */
|
||
+@ApiStatus.Internal
|
||
+public class SpigotPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup {
|
||
+
|
||
+ private final Predicate<ConfiguredPluginClassLoader> libraryClassloaderPredicate;
|
||
+
|
||
+ public SpigotPluginClassLoaderGroup(GlobalPluginClassLoaderGroup globalPluginClassLoaderGroup, Predicate<ConfiguredPluginClassLoader> libraryClassloaderPredicate) {
|
||
+ super(globalPluginClassLoaderGroup.getClassLoaders());
|
||
+ this.libraryClassloaderPredicate = libraryClassloaderPredicate;
|
||
+ }
|
||
+
|
||
+ // Mirrors global list
|
||
+ @Override
|
||
+ public void add(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void remove(ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ protected Class<?> lookupClass(String name, boolean resolve, ConfiguredPluginClassLoader current) throws ClassNotFoundException {
|
||
+ return current.loadClass(name, resolve, false, this.libraryClassloaderPredicate.test(current));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ClassLoaderAccess getAccess() {
|
||
+ return v -> true;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "SpigotPluginClassLoaderGroup{" +
|
||
+ "libraryClassloaderPredicate=" + this.libraryClassloaderPredicate +
|
||
+ ", classloaders=" + this.classloaders +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/StaticPluginClassLoaderGroup.java b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/StaticPluginClassLoaderGroup.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..7f7085b06271adf8a37485f4c9c9b8af605dd27d
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/classloader/group/StaticPluginClassLoaderGroup.java
|
||
@@ -0,0 +1,31 @@
|
||
+package io.papermc.paper.plugin.entrypoint.classloader.group;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.classloader.ClassLoaderAccess;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+import java.util.List;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public class StaticPluginClassLoaderGroup extends SimpleListPluginClassLoaderGroup {
|
||
+
|
||
+ private final ClassLoaderAccess access;
|
||
+
|
||
+ public StaticPluginClassLoaderGroup(List<ConfiguredPluginClassLoader> classloaders, ClassLoaderAccess access) {
|
||
+ super(classloaders);
|
||
+ this.access = access;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ClassLoaderAccess getAccess() {
|
||
+ return this.access;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "StaticPluginClassLoaderGroup{" +
|
||
+ "access=" + this.access +
|
||
+ ", classloaders=" + this.classloaders +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyContextHolder.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyContextHolder.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..f43295fdeaa587cf30c35a1d545167071d58ce4b
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyContextHolder.java
|
||
@@ -0,0 +1,9 @@
|
||
+package io.papermc.paper.plugin.entrypoint.dependency;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+
|
||
+public interface DependencyContextHolder {
|
||
+
|
||
+ void setContext(DependencyContext context);
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..1af3c3434eb1f3b00857b17a07f42e51086c1e2b
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/DependencyUtil.java
|
||
@@ -0,0 +1,45 @@
|
||
+package io.papermc.paper.plugin.entrypoint.dependency;
|
||
+
|
||
+import com.google.common.graph.MutableGraph;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+
|
||
+@SuppressWarnings("UnstableApiUsage")
|
||
+public class DependencyUtil {
|
||
+
|
||
+ @NotNull
|
||
+ public static MutableGraph<String> buildDependencyGraph(@NotNull MutableGraph<String> dependencyGraph, @NotNull PluginMeta configuration) {
|
||
+ List<String> dependencies = new ArrayList<>();
|
||
+ dependencies.addAll(configuration.getPluginDependencies());
|
||
+ dependencies.addAll(configuration.getPluginSoftDependencies());
|
||
+
|
||
+ return buildDependencyGraph(dependencyGraph, configuration.getName(), dependencies, configuration.getLoadBeforePlugins());
|
||
+ }
|
||
+
|
||
+ @NotNull
|
||
+ public static MutableGraph<String> buildDependencyGraph(@NotNull MutableGraph<String> dependencyGraph, String identifier, @NotNull Iterable<String> depends, @NotNull Iterable<String> loadBefore) {
|
||
+ for (String dependency : depends) {
|
||
+ dependencyGraph.putEdge(identifier, dependency);
|
||
+ }
|
||
+
|
||
+ for (String loadBeforeTarget : loadBefore) {
|
||
+ dependencyGraph.putEdge(loadBeforeTarget, identifier);
|
||
+ }
|
||
+
|
||
+ dependencyGraph.addNode(identifier); // Make sure dependencies at least have a node
|
||
+ return dependencyGraph;
|
||
+ }
|
||
+
|
||
+ // This adds a provided plugin to another plugin, basically making it seem like a "dependency"
|
||
+ // in order to have plugins that need the provided plugin to load after the specified plugin name
|
||
+ @NotNull
|
||
+ public static MutableGraph<String> addProvidedPlugin(@NotNull MutableGraph<String> dependencyGraph, @NotNull String pluginName, @NotNull String providedName) {
|
||
+ dependencyGraph.putEdge(pluginName, providedName);
|
||
+
|
||
+ return dependencyGraph;
|
||
+ }
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..6f201a8131ca9631ac4af62c75e6f2e889cb5eae
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/dependency/GraphDependencyContext.java
|
||
@@ -0,0 +1,43 @@
|
||
+package io.papermc.paper.plugin.entrypoint.dependency;
|
||
+
|
||
+import com.google.common.graph.Graph;
|
||
+import com.google.common.graph.Graphs;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+
|
||
+import java.util.Set;
|
||
+
|
||
+@SuppressWarnings("UnstableApiUsage")
|
||
+public class GraphDependencyContext implements DependencyContext {
|
||
+
|
||
+ private final Graph<String> dependencyGraph;
|
||
+
|
||
+ public GraphDependencyContext(Graph<String> dependencyGraph) {
|
||
+ this.dependencyGraph = dependencyGraph;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean isTransitiveDependency(PluginMeta plugin, PluginMeta depend) {
|
||
+ String pluginIdentifier = plugin.getName();
|
||
+
|
||
+ if (this.dependencyGraph.nodes().contains(pluginIdentifier)) {
|
||
+ Set<String> reachableNodes = Graphs.reachableNodes(this.dependencyGraph, pluginIdentifier);
|
||
+ if (reachableNodes.contains(depend.getName())) {
|
||
+ return true;
|
||
+ }
|
||
+ for (String provided : depend.getProvidedPlugins()) {
|
||
+ if (reachableNodes.contains(provided)) {
|
||
+ return true;
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean hasDependency(String pluginIdentifier) {
|
||
+ return this.dependencyGraph.nodes().contains(pluginIdentifier);
|
||
+ }
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..22189a1c42459c00d3e8bdeb980d15a69b720805
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/JohnsonSimpleCycles.java
|
||
@@ -0,0 +1,350 @@
|
||
+/*
|
||
+ * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors.
|
||
+ *
|
||
+ * JGraphT : a free Java graph-theory library
|
||
+ *
|
||
+ * See the CONTRIBUTORS.md file distributed with this work for additional
|
||
+ * information regarding copyright ownership.
|
||
+ *
|
||
+ * This program and the accompanying materials are made available under the
|
||
+ * terms of the Eclipse Public License 2.0 which is available at
|
||
+ * http://www.eclipse.org/legal/epl-2.0, or the
|
||
+ * GNU Lesser General Public License v2.1 or later
|
||
+ * which is available at
|
||
+ * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
|
||
+ *
|
||
+ * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
|
||
+ */
|
||
+
|
||
+// MODIFICATIONS:
|
||
+// - Modified to use a guava graph directly
|
||
+
|
||
+package io.papermc.paper.plugin.entrypoint.strategy;
|
||
+
|
||
+import com.google.common.base.Preconditions;
|
||
+import com.google.common.graph.Graph;
|
||
+import com.google.common.graph.GraphBuilder;
|
||
+import com.google.common.graph.MutableGraph;
|
||
+import com.mojang.datafixers.util.Pair;
|
||
+
|
||
+import java.util.ArrayDeque;
|
||
+import java.util.ArrayList;
|
||
+import java.util.HashMap;
|
||
+import java.util.HashSet;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+import java.util.Set;
|
||
+import java.util.function.Consumer;
|
||
+
|
||
+/**
|
||
+ * Find all simple cycles of a directed graph using the Johnson's algorithm.
|
||
+ *
|
||
+ * <p>
|
||
+ * See:<br>
|
||
+ * D.B.Johnson, Finding all the elementary circuits of a directed graph, SIAM J. Comput., 4 (1975),
|
||
+ * pp. 77-84.
|
||
+ *
|
||
+ * @param <V> the vertex type.
|
||
+ *
|
||
+ * @author Nikolay Ognyanov
|
||
+ */
|
||
+public class JohnsonSimpleCycles<V>
|
||
+{
|
||
+ // The graph.
|
||
+ private Graph<V> graph;
|
||
+
|
||
+ // The main state of the algorithm.
|
||
+ private Consumer<List<V>> cycleConsumer = null;
|
||
+ private V[] iToV = null;
|
||
+ private Map<V, Integer> vToI = null;
|
||
+ private Set<V> blocked = null;
|
||
+ private Map<V, Set<V>> bSets = null;
|
||
+ private ArrayDeque<V> stack = null;
|
||
+
|
||
+ // The state of the embedded Tarjan SCC algorithm.
|
||
+ private List<Set<V>> foundSCCs = null;
|
||
+ private int index = 0;
|
||
+ private Map<V, Integer> vIndex = null;
|
||
+ private Map<V, Integer> vLowlink = null;
|
||
+ private ArrayDeque<V> path = null;
|
||
+ private Set<V> pathSet = null;
|
||
+
|
||
+ /**
|
||
+ * Create a simple cycle finder for the specified graph.
|
||
+ *
|
||
+ * @param graph - the DirectedGraph in which to find cycles.
|
||
+ *
|
||
+ * @throws IllegalArgumentException if the graph argument is <code>
|
||
+ * null</code>.
|
||
+ */
|
||
+ public JohnsonSimpleCycles(Graph<V> graph)
|
||
+ {
|
||
+ Preconditions.checkState(graph.isDirected(), "Graph must be directed");
|
||
+ this.graph = graph;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Find the simple cycles of the graph.
|
||
+ *
|
||
+ * @return The list of all simple cycles. Possibly empty but never <code>null</code>.
|
||
+ */
|
||
+ public List<List<V>> findSimpleCycles()
|
||
+ {
|
||
+ List<List<V>> result = new ArrayList<>();
|
||
+ findSimpleCycles(result::add);
|
||
+ return result;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Find the simple cycles of the graph.
|
||
+ *
|
||
+ * @param consumer Consumer that will be called with each cycle found.
|
||
+ */
|
||
+ public void findSimpleCycles(Consumer<List<V>> consumer)
|
||
+ {
|
||
+ if (graph == null) {
|
||
+ throw new IllegalArgumentException("Null graph.");
|
||
+ }
|
||
+ initState(consumer);
|
||
+
|
||
+ int startIndex = 0;
|
||
+ int size = graph.nodes().size();
|
||
+ while (startIndex < size) {
|
||
+ Pair<Graph<V>, Integer> minSCCGResult = findMinSCSG(startIndex);
|
||
+ if (minSCCGResult != null) {
|
||
+ startIndex = minSCCGResult.getSecond();
|
||
+ Graph<V> scg = minSCCGResult.getFirst();
|
||
+ V startV = toV(startIndex);
|
||
+ for (V v : scg.successors(startV)) {
|
||
+ blocked.remove(v);
|
||
+ getBSet(v).clear();
|
||
+ }
|
||
+ findCyclesInSCG(startIndex, startIndex, scg);
|
||
+ startIndex++;
|
||
+ } else {
|
||
+ break;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ clearState();
|
||
+ }
|
||
+
|
||
+ private Pair<Graph<V>, Integer> findMinSCSG(int startIndex)
|
||
+ {
|
||
+ /*
|
||
+ * Per Johnson : "adjacency structure of strong component $K$ with least vertex in subgraph
|
||
+ * of $G$ induced by $(s, s + 1, n)$". Or in contemporary terms: the strongly connected
|
||
+ * component of the subgraph induced by $(v_1, \dotso ,v_n)$ which contains the minimum
|
||
+ * (among those SCCs) vertex index. We return that index together with the graph.
|
||
+ */
|
||
+ initMinSCGState();
|
||
+
|
||
+ List<Set<V>> foundSCCs = findSCCS(startIndex);
|
||
+
|
||
+ // find the SCC with the minimum index
|
||
+ int minIndexFound = Integer.MAX_VALUE;
|
||
+ Set<V> minSCC = null;
|
||
+ for (Set<V> scc : foundSCCs) {
|
||
+ for (V v : scc) {
|
||
+ int t = toI(v);
|
||
+ if (t < minIndexFound) {
|
||
+ minIndexFound = t;
|
||
+ minSCC = scc;
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ if (minSCC == null) {
|
||
+ return null;
|
||
+ }
|
||
+
|
||
+ // build a graph for the SCC found
|
||
+ MutableGraph<V> dependencyGraph = GraphBuilder.directed().allowsSelfLoops(true).build();
|
||
+
|
||
+ for (V v : minSCC) {
|
||
+ for (V w : minSCC) {
|
||
+ if (graph.hasEdgeConnecting(v, w)) {
|
||
+ dependencyGraph.putEdge(v, w);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ Pair<Graph<V>, Integer> result = Pair.of(dependencyGraph, minIndexFound);
|
||
+ clearMinSCCState();
|
||
+ return result;
|
||
+ }
|
||
+
|
||
+ private List<Set<V>> findSCCS(int startIndex)
|
||
+ {
|
||
+ // Find SCCs in the subgraph induced
|
||
+ // by vertices startIndex and beyond.
|
||
+ // A call to StrongConnectivityAlgorithm
|
||
+ // would be too expensive because of the
|
||
+ // need to materialize the subgraph.
|
||
+ // So - do a local search by the Tarjan's
|
||
+ // algorithm and pretend that vertices
|
||
+ // with an index smaller than startIndex
|
||
+ // do not exist.
|
||
+ for (V v : graph.nodes()) {
|
||
+ int vI = toI(v);
|
||
+ if (vI < startIndex) {
|
||
+ continue;
|
||
+ }
|
||
+ if (!vIndex.containsKey(v)) {
|
||
+ getSCCs(startIndex, vI);
|
||
+ }
|
||
+ }
|
||
+ List<Set<V>> result = foundSCCs;
|
||
+ foundSCCs = null;
|
||
+ return result;
|
||
+ }
|
||
+
|
||
+ private void getSCCs(int startIndex, int vertexIndex)
|
||
+ {
|
||
+ V vertex = toV(vertexIndex);
|
||
+ vIndex.put(vertex, index);
|
||
+ vLowlink.put(vertex, index);
|
||
+ index++;
|
||
+ path.push(vertex);
|
||
+ pathSet.add(vertex);
|
||
+
|
||
+ Set<V> edges = graph.successors(vertex);
|
||
+ for (V successor : edges) {
|
||
+ int successorIndex = toI(successor);
|
||
+ if (successorIndex < startIndex) {
|
||
+ continue;
|
||
+ }
|
||
+ if (!vIndex.containsKey(successor)) {
|
||
+ getSCCs(startIndex, successorIndex);
|
||
+ vLowlink.put(vertex, Math.min(vLowlink.get(vertex), vLowlink.get(successor)));
|
||
+ } else if (pathSet.contains(successor)) {
|
||
+ vLowlink.put(vertex, Math.min(vLowlink.get(vertex), vIndex.get(successor)));
|
||
+ }
|
||
+ }
|
||
+ if (vLowlink.get(vertex).equals(vIndex.get(vertex))) {
|
||
+ Set<V> result = new HashSet<>();
|
||
+ V temp;
|
||
+ do {
|
||
+ temp = path.pop();
|
||
+ pathSet.remove(temp);
|
||
+ result.add(temp);
|
||
+ } while (!vertex.equals(temp));
|
||
+ if (result.size() == 1) {
|
||
+ V v = result.iterator().next();
|
||
+ if (graph.edges().contains(vertex)) {
|
||
+ foundSCCs.add(result);
|
||
+ }
|
||
+ } else {
|
||
+ foundSCCs.add(result);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private boolean findCyclesInSCG(int startIndex, int vertexIndex, Graph<V> scg)
|
||
+ {
|
||
+ /*
|
||
+ * Find cycles in a strongly connected graph per Johnson.
|
||
+ */
|
||
+ boolean foundCycle = false;
|
||
+ V vertex = toV(vertexIndex);
|
||
+ stack.push(vertex);
|
||
+ blocked.add(vertex);
|
||
+
|
||
+ for (V successor : scg.successors(vertex)) {
|
||
+ int successorIndex = toI(successor);
|
||
+ if (successorIndex == startIndex) {
|
||
+ List<V> cycle = new ArrayList<>(stack.size());
|
||
+ stack.descendingIterator().forEachRemaining(cycle::add);
|
||
+ cycleConsumer.accept(cycle);
|
||
+ foundCycle = true;
|
||
+ } else if (!blocked.contains(successor)) {
|
||
+ boolean gotCycle = findCyclesInSCG(startIndex, successorIndex, scg);
|
||
+ foundCycle = foundCycle || gotCycle;
|
||
+ }
|
||
+ }
|
||
+ if (foundCycle) {
|
||
+ unblock(vertex);
|
||
+ } else {
|
||
+ for (V w : scg.successors(vertex)) {
|
||
+ Set<V> bSet = getBSet(w);
|
||
+ bSet.add(vertex);
|
||
+ }
|
||
+ }
|
||
+ stack.pop();
|
||
+ return foundCycle;
|
||
+ }
|
||
+
|
||
+ private void unblock(V vertex)
|
||
+ {
|
||
+ blocked.remove(vertex);
|
||
+ Set<V> bSet = getBSet(vertex);
|
||
+ while (bSet.size() > 0) {
|
||
+ V w = bSet.iterator().next();
|
||
+ bSet.remove(w);
|
||
+ if (blocked.contains(w)) {
|
||
+ unblock(w);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @SuppressWarnings("unchecked")
|
||
+ private void initState(Consumer<List<V>> consumer)
|
||
+ {
|
||
+ cycleConsumer = consumer;
|
||
+ iToV = (V[]) graph.nodes().toArray();
|
||
+ vToI = new HashMap<>();
|
||
+ blocked = new HashSet<>();
|
||
+ bSets = new HashMap<>();
|
||
+ stack = new ArrayDeque<>();
|
||
+
|
||
+ for (int i = 0; i < iToV.length; i++) {
|
||
+ vToI.put(iToV[i], i);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private void clearState()
|
||
+ {
|
||
+ cycleConsumer = null;
|
||
+ iToV = null;
|
||
+ vToI = null;
|
||
+ blocked = null;
|
||
+ bSets = null;
|
||
+ stack = null;
|
||
+ }
|
||
+
|
||
+ private void initMinSCGState()
|
||
+ {
|
||
+ index = 0;
|
||
+ foundSCCs = new ArrayList<>();
|
||
+ vIndex = new HashMap<>();
|
||
+ vLowlink = new HashMap<>();
|
||
+ path = new ArrayDeque<>();
|
||
+ pathSet = new HashSet<>();
|
||
+ }
|
||
+
|
||
+ private void clearMinSCCState()
|
||
+ {
|
||
+ index = 0;
|
||
+ foundSCCs = null;
|
||
+ vIndex = null;
|
||
+ vLowlink = null;
|
||
+ path = null;
|
||
+ pathSet = null;
|
||
+ }
|
||
+
|
||
+ private Integer toI(V vertex)
|
||
+ {
|
||
+ return vToI.get(vertex);
|
||
+ }
|
||
+
|
||
+ private V toV(Integer i)
|
||
+ {
|
||
+ return iToV[i];
|
||
+ }
|
||
+
|
||
+ private Set<V> getBSet(V v)
|
||
+ {
|
||
+ // B sets typically not all needed,
|
||
+ // so instantiate lazily.
|
||
+ return bSets.computeIfAbsent(v, k -> new HashSet<>());
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..8bc11d7e9341a31382c1b055565cc8d5fd3203ea
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/LegacyPluginLoadingStrategy.java
|
||
@@ -0,0 +1,260 @@
|
||
+package io.papermc.paper.plugin.entrypoint.strategy;
|
||
+
|
||
+import com.google.common.graph.GraphBuilder;
|
||
+import com.google.common.graph.MutableGraph;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import org.bukkit.plugin.UnknownDependencyException;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.Collection;
|
||
+import java.util.HashMap;
|
||
+import java.util.HashSet;
|
||
+import java.util.Iterator;
|
||
+import java.util.LinkedList;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+import java.util.Set;
|
||
+import java.util.logging.Level;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+@SuppressWarnings("UnstableApiUsage")
|
||
+public class LegacyPluginLoadingStrategy<T> implements ProviderLoadingStrategy<T> {
|
||
+
|
||
+ private static final Logger LOGGER = Logger.getLogger("LegacyPluginLoadingStrategy");
|
||
+ private final ProviderConfiguration<T> configuration;
|
||
+
|
||
+ public LegacyPluginLoadingStrategy(ProviderConfiguration<T> onLoad) {
|
||
+ this.configuration = onLoad;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<T> loadProviders(List<PluginProvider<T>> providers) {
|
||
+ List<T> javapluginsLoaded = new ArrayList<>();
|
||
+ MutableGraph<String> dependencyGraph = GraphBuilder.directed().build();
|
||
+ GraphDependencyContext dependencyContext = new GraphDependencyContext(dependencyGraph);
|
||
+
|
||
+ Map<String, PluginProvider<T>> providersToLoad = new HashMap<>();
|
||
+ Set<String> loadedPlugins = new HashSet<>();
|
||
+ Map<String, String> pluginsProvided = new HashMap<>();
|
||
+ Map<String, Collection<String>> dependencies = new HashMap<>();
|
||
+ Map<String, Collection<String>> softDependencies = new HashMap<>();
|
||
+
|
||
+ for (PluginProvider<T> provider : providers) {
|
||
+ PluginMeta configuration = provider.getMeta();
|
||
+
|
||
+ PluginProvider<T> replacedProvider = providersToLoad.put(configuration.getName(), provider);
|
||
+ if (replacedProvider != null) {
|
||
+ LOGGER.severe(String.format(
|
||
+ "Ambiguous plugin name `%s' for files `%s' and `%s' in `%s'",
|
||
+ configuration.getName(),
|
||
+ provider.getSource(),
|
||
+ replacedProvider.getSource(),
|
||
+ replacedProvider.getParentSource()
|
||
+ ));
|
||
+ }
|
||
+
|
||
+ String removedProvided = pluginsProvided.remove(configuration.getName());
|
||
+ if (removedProvided != null) {
|
||
+ LOGGER.warning(String.format(
|
||
+ "Ambiguous plugin name `%s'. It is also provided by `%s'",
|
||
+ configuration.getName(),
|
||
+ removedProvided
|
||
+ ));
|
||
+ }
|
||
+
|
||
+ for (String provided : configuration.getProvidedPlugins()) {
|
||
+ PluginProvider<T> pluginProvider = providersToLoad.get(provided);
|
||
+
|
||
+ if (pluginProvider != null) {
|
||
+ LOGGER.warning(String.format(
|
||
+ "`%s provides `%s' while this is also the name of `%s' in `%s'",
|
||
+ provider.getSource(),
|
||
+ provided,
|
||
+ pluginProvider.getSource(),
|
||
+ provider.getParentSource()
|
||
+ ));
|
||
+ } else {
|
||
+ String replacedPlugin = pluginsProvided.put(provided, configuration.getName());
|
||
+ if (replacedPlugin != null) {
|
||
+ LOGGER.warning(String.format(
|
||
+ "`%s' is provided by both `%s' and `%s'",
|
||
+ provided,
|
||
+ configuration.getName(),
|
||
+ replacedPlugin
|
||
+ ));
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ Collection<String> softDependencySet = this.configuration.optionalDependencies(provider);
|
||
+ if (softDependencySet != null && !softDependencySet.isEmpty()) {
|
||
+ if (softDependencies.containsKey(configuration.getName())) {
|
||
+ // Duplicates do not matter, they will be removed together if applicable
|
||
+ softDependencies.get(configuration.getName()).addAll(softDependencySet);
|
||
+ } else {
|
||
+ softDependencies.put(configuration.getName(), new LinkedList<String>(softDependencySet));
|
||
+ }
|
||
+
|
||
+ for (String depend : softDependencySet) {
|
||
+ dependencyGraph.putEdge(configuration.getName(), depend);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ Collection<String> dependencySet = this.configuration.requiredDependencies(provider);
|
||
+ if (dependencySet != null && !dependencySet.isEmpty()) {
|
||
+ dependencies.put(configuration.getName(), new LinkedList<String>(dependencySet));
|
||
+
|
||
+ for (String depend : dependencySet) {
|
||
+ dependencyGraph.putEdge(configuration.getName(), depend);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ Collection<String> loadBeforeSet = this.configuration.loadBeforeDependencies(provider);
|
||
+ if (loadBeforeSet != null && !loadBeforeSet.isEmpty()) {
|
||
+ for (String loadBeforeTarget : loadBeforeSet) {
|
||
+ if (softDependencies.containsKey(loadBeforeTarget)) {
|
||
+ softDependencies.get(loadBeforeTarget).add(configuration.getName());
|
||
+ } else {
|
||
+ // softDependencies is never iterated, so 'ghost' plugins aren't an issue
|
||
+ Collection<String> shortSoftDependency = new LinkedList<String>();
|
||
+ shortSoftDependency.add(configuration.getName());
|
||
+ softDependencies.put(loadBeforeTarget, shortSoftDependency);
|
||
+ }
|
||
+
|
||
+ dependencyGraph.putEdge(loadBeforeTarget, configuration.getName());
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ while (!providersToLoad.isEmpty()) {
|
||
+ boolean missingDependency = true;
|
||
+ Iterator<Map.Entry<String, PluginProvider<T>>> providerIterator = providersToLoad.entrySet().iterator();
|
||
+
|
||
+ while (providerIterator.hasNext()) {
|
||
+ Map.Entry<String, PluginProvider<T>> entry = providerIterator.next();
|
||
+ String providerIdentifier = entry.getKey();
|
||
+
|
||
+ if (dependencies.containsKey(providerIdentifier)) {
|
||
+ Iterator<String> dependencyIterator = dependencies.get(providerIdentifier).iterator();
|
||
+ final Set<String> missingHardDependencies = new HashSet<>(dependencies.get(providerIdentifier).size()); // Paper - list all missing hard depends
|
||
+
|
||
+ while (dependencyIterator.hasNext()) {
|
||
+ String dependency = dependencyIterator.next();
|
||
+
|
||
+ // Dependency loaded
|
||
+ if (loadedPlugins.contains(dependency)) {
|
||
+ dependencyIterator.remove();
|
||
+
|
||
+ // We have a dependency not found
|
||
+ } else if (!providersToLoad.containsKey(dependency) && !pluginsProvided.containsKey(dependency)) {
|
||
+ // Paper start
|
||
+ missingHardDependencies.add(dependency);
|
||
+ }
|
||
+ }
|
||
+ if (!missingHardDependencies.isEmpty()) {
|
||
+ // Paper end
|
||
+ missingDependency = false;
|
||
+ providerIterator.remove();
|
||
+ pluginsProvided.values().removeIf(s -> s.equals(providerIdentifier)); // Paper - remove provided plugins
|
||
+ softDependencies.remove(providerIdentifier);
|
||
+ dependencies.remove(providerIdentifier);
|
||
+
|
||
+ LOGGER.log(
|
||
+ Level.SEVERE,
|
||
+ "Could not load '" + entry.getValue().getSource() + "' in folder '" + entry.getValue().getParentSource() + "'", // Paper
|
||
+ new UnknownDependencyException(missingHardDependencies, providerIdentifier)); // Paper
|
||
+ }
|
||
+
|
||
+ if (dependencies.containsKey(providerIdentifier) && dependencies.get(providerIdentifier).isEmpty()) {
|
||
+ dependencies.remove(providerIdentifier);
|
||
+ }
|
||
+ }
|
||
+ if (softDependencies.containsKey(providerIdentifier)) {
|
||
+ Iterator<String> softDependencyIterator = softDependencies.get(providerIdentifier).iterator();
|
||
+
|
||
+ while (softDependencyIterator.hasNext()) {
|
||
+ String softDependency = softDependencyIterator.next();
|
||
+
|
||
+ // Soft depend is no longer around
|
||
+ if (!providersToLoad.containsKey(softDependency) && !pluginsProvided.containsKey(softDependency)) {
|
||
+ softDependencyIterator.remove();
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (softDependencies.get(providerIdentifier).isEmpty()) {
|
||
+ softDependencies.remove(providerIdentifier);
|
||
+ }
|
||
+ }
|
||
+ if (!(dependencies.containsKey(providerIdentifier) || softDependencies.containsKey(providerIdentifier)) && providersToLoad.containsKey(providerIdentifier)) {
|
||
+ // We're clear to load, no more soft or hard dependencies left
|
||
+ PluginProvider<T> file = providersToLoad.get(providerIdentifier);
|
||
+ providerIterator.remove();
|
||
+ pluginsProvided.values().removeIf(s -> s.equals(providerIdentifier)); // Paper - remove provided plugins
|
||
+ missingDependency = false;
|
||
+
|
||
+ try {
|
||
+ this.configuration.applyContext(file, dependencyContext);
|
||
+ T loadedPlugin = file.createInstance();
|
||
+
|
||
+ if (this.configuration.load(file, loadedPlugin)) {
|
||
+ loadedPlugins.add(file.getMeta().getName());
|
||
+ loadedPlugins.addAll(file.getMeta().getProvidedPlugins());
|
||
+ javapluginsLoaded.add(loadedPlugin);
|
||
+ }
|
||
+
|
||
+ } catch (Exception ex) {
|
||
+ LOGGER.log(Level.SEVERE, "Could not load '" + file.getSource() + "' in folder '" + file.getParentSource() + "'", ex); // Paper
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (missingDependency) {
|
||
+ // We now iterate over plugins until something loads
|
||
+ // This loop will ignore soft dependencies
|
||
+ providerIterator = providersToLoad.entrySet().iterator();
|
||
+
|
||
+ while (providerIterator.hasNext()) {
|
||
+ Map.Entry<String, PluginProvider<T>> entry = providerIterator.next();
|
||
+ String plugin = entry.getKey();
|
||
+
|
||
+ if (!dependencies.containsKey(plugin)) {
|
||
+ softDependencies.remove(plugin);
|
||
+ missingDependency = false;
|
||
+ PluginProvider<T> file = entry.getValue();
|
||
+ providerIterator.remove();
|
||
+
|
||
+ try {
|
||
+ this.configuration.applyContext(file, dependencyContext);
|
||
+ T loadedPlugin = file.createInstance();
|
||
+
|
||
+ if (this.configuration.load(file, loadedPlugin)) {
|
||
+ loadedPlugins.add(file.getMeta().getName());
|
||
+ loadedPlugins.addAll(file.getMeta().getProvidedPlugins());
|
||
+ javapluginsLoaded.add(loadedPlugin);
|
||
+ }
|
||
+ break;
|
||
+ } catch (Exception ex) {
|
||
+ LOGGER.log(Level.SEVERE, "Could not load '" + file.getSource() + "' in folder '" + file.getParentSource() + "'", ex); // Paper
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ // We have no plugins left without a depend
|
||
+ if (missingDependency) {
|
||
+ softDependencies.clear();
|
||
+ dependencies.clear();
|
||
+ Iterator<PluginProvider<T>> failedPluginIterator = providersToLoad.values().iterator();
|
||
+
|
||
+ while (failedPluginIterator.hasNext()) {
|
||
+ PluginProvider<T> file = failedPluginIterator.next();
|
||
+ failedPluginIterator.remove();
|
||
+ LOGGER.log(Level.SEVERE, "Could not load '" + file.getSource() + "' in folder '" + file.getParentSource() + "': circular dependency detected"); // Paper
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return javapluginsLoaded;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..a0363af157131a2b42c00028a260e9c3d3c543de
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ModernPluginLoadingStrategy.java
|
||
@@ -0,0 +1,143 @@
|
||
+package io.papermc.paper.plugin.entrypoint.strategy;
|
||
+
|
||
+import com.google.common.collect.Lists;
|
||
+import com.google.common.graph.GraphBuilder;
|
||
+import com.google.common.graph.MutableGraph;
|
||
+import com.mojang.logging.LogUtils;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import org.bukkit.plugin.UnknownDependencyException;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.HashMap;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+
|
||
+@SuppressWarnings("UnstableApiUsage")
|
||
+public class ModernPluginLoadingStrategy<T> implements ProviderLoadingStrategy<T> {
|
||
+
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+ private final ProviderConfiguration<T> configuration;
|
||
+
|
||
+ public ModernPluginLoadingStrategy(ProviderConfiguration<T> onLoad) {
|
||
+ this.configuration = onLoad;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<T> loadProviders(List<PluginProvider<T>> pluginProviders) {
|
||
+ MutableGraph<String> dependencyGraph = GraphBuilder.directed().build();
|
||
+ Map<String, PluginProviderEntry<T>> providerMap = new HashMap<>();
|
||
+ List<PluginProvider<T>> validatedProviders = new ArrayList<>();
|
||
+
|
||
+ // Populate provider map
|
||
+ for (PluginProvider<T> provider : pluginProviders) {
|
||
+ PluginMeta providerConfig = provider.getMeta();
|
||
+ PluginProviderEntry<T> entry = new PluginProviderEntry<>(provider);
|
||
+
|
||
+ PluginProviderEntry<T> replacedProvider = providerMap.put(providerConfig.getName(), entry);
|
||
+ if (replacedProvider != null) {
|
||
+ LOGGER.error(String.format(
|
||
+ "Ambiguous plugin name '%s' for files '%s' and '%s' in '%s'",
|
||
+ providerConfig.getName(),
|
||
+ provider.getSource(),
|
||
+ replacedProvider.provider.getSource(),
|
||
+ replacedProvider.provider.getParentSource()
|
||
+ ));
|
||
+ }
|
||
+
|
||
+ for (String extra : providerConfig.getProvidedPlugins()) {
|
||
+ PluginProviderEntry<T> replacedExtraProvider = providerMap.putIfAbsent(extra, entry);
|
||
+ if (replacedExtraProvider != null) {
|
||
+ LOGGER.warn(String.format(
|
||
+ "`%s' is provided by both `%s' and `%s'",
|
||
+ extra,
|
||
+ providerConfig.getName(),
|
||
+ replacedExtraProvider.provider.getMeta().getName()
|
||
+ ));
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Validate providers, ensuring all of them have valid dependencies. Removing those who are invalid
|
||
+ for (PluginProvider<T> provider : pluginProviders) {
|
||
+ PluginMeta configuration = provider.getMeta();
|
||
+
|
||
+ // Populate missing dependencies to capture if there are multiple missing ones.
|
||
+ List<String> missingDependencies = new ArrayList<>();
|
||
+ for (String hardDependency : this.configuration.requiredDependencies(provider)) {
|
||
+ if (!providerMap.containsKey(hardDependency)) {
|
||
+ missingDependencies.add(hardDependency);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ if (missingDependencies.isEmpty()) {
|
||
+ validatedProviders.add(provider);
|
||
+ } else {
|
||
+ LOGGER.error("Could not load '%s' in '%s'".formatted(provider.getSource(), provider.getParentSource()), new UnknownDependencyException(missingDependencies, configuration.getName())); // Paper
|
||
+ // Because the validator is invalid, remove it from the provider map
|
||
+ providerMap.remove(configuration.getName());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ for (PluginProvider<?> validated : validatedProviders) {
|
||
+ PluginMeta configuration = validated.getMeta();
|
||
+
|
||
+ // Build a validated provider's dependencies into the graph
|
||
+ DependencyUtil.buildDependencyGraph(dependencyGraph, configuration);
|
||
+
|
||
+ // Add the provided plugins to the graph as well
|
||
+ for (String provides : configuration.getProvidedPlugins()) {
|
||
+ DependencyUtil.addProvidedPlugin(dependencyGraph, configuration.getName(), provides);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Reverse the topographic search to let us see which providers we can load first.
|
||
+ List<String> reversedTopographicSort;
|
||
+ try {
|
||
+ reversedTopographicSort = Lists.reverse(TopographicGraphSorter.sortGraph(dependencyGraph));
|
||
+ } catch (TopographicGraphSorter.GraphCycleException exception) {
|
||
+ throw new PluginGraphCycleException(new JohnsonSimpleCycles<>(dependencyGraph).findSimpleCycles());
|
||
+ }
|
||
+
|
||
+ GraphDependencyContext graphDependencyContext = new GraphDependencyContext(dependencyGraph);
|
||
+ List<T> loadedPlugins = new ArrayList<>();
|
||
+ for (String providerIdentifier : reversedTopographicSort) {
|
||
+ // It's possible that this will be null because the above dependencies for soft/load before aren't validated if they exist.
|
||
+ // The graph could be MutableGraph<PluginProvider<T>>, but we would have to check if each dependency exists there... just
|
||
+ // nicer to do it here TBH.
|
||
+ PluginProviderEntry<T> retrievedProviderEntry = providerMap.get(providerIdentifier);
|
||
+ if (retrievedProviderEntry == null || retrievedProviderEntry.provided) {
|
||
+ // OR if this was already provided (most likely from a plugin that already "provides" that dependency)
|
||
+ // This won't matter since the provided plugin is loaded as a dependency, meaning it should have been loaded correctly anyways
|
||
+ continue; // Skip provider that doesn't exist....
|
||
+ }
|
||
+ retrievedProviderEntry.provided = true;
|
||
+ PluginProvider<T> retrievedProvider = retrievedProviderEntry.provider;
|
||
+ try {
|
||
+ this.configuration.applyContext(retrievedProvider, graphDependencyContext);
|
||
+
|
||
+ T instance = retrievedProvider.createInstance();
|
||
+ if (this.configuration.load(retrievedProvider, instance)) {
|
||
+ loadedPlugins.add(instance);
|
||
+ }
|
||
+ } catch (Exception ex) {
|
||
+ LOGGER.error("Could not load plugin '%s' in folder '%s'".formatted(retrievedProvider.getFileName(), retrievedProvider.getParentSource()), ex); // Paper
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return loadedPlugins;
|
||
+ }
|
||
+
|
||
+ private static class PluginProviderEntry<T> {
|
||
+
|
||
+ private final PluginProvider<T> provider;
|
||
+ private boolean provided;
|
||
+
|
||
+ private PluginProviderEntry(PluginProvider<T> provider) {
|
||
+ this.provider = provider;
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..2ea978ac957849260e7ca69c9ff56588d0ccc41b
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/PluginGraphCycleException.java
|
||
@@ -0,0 +1,19 @@
|
||
+package io.papermc.paper.plugin.entrypoint.strategy;
|
||
+
|
||
+import java.util.List;
|
||
+
|
||
+/**
|
||
+ * Indicates a dependency cycle within a provider loading sequence.
|
||
+ */
|
||
+public class PluginGraphCycleException extends RuntimeException {
|
||
+
|
||
+ private final List<List<String>> cycles;
|
||
+
|
||
+ public PluginGraphCycleException(List<List<String>> cycles) {
|
||
+ this.cycles = cycles;
|
||
+ }
|
||
+
|
||
+ public List<List<String>> getCycles() {
|
||
+ return this.cycles;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..5e18616160014d70df2b539d7e65bb003ac7a4b7
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration.java
|
||
@@ -0,0 +1,26 @@
|
||
+package io.papermc.paper.plugin.entrypoint.strategy;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+
|
||
+import java.util.List;
|
||
+
|
||
+/**
|
||
+ * Used to share code with the modern and legacy plugin load strategy.
|
||
+ *
|
||
+ * @param <T>
|
||
+ */
|
||
+public interface ProviderConfiguration<T> {
|
||
+
|
||
+ void applyContext(PluginProvider<T> provider, DependencyContext dependencyContext);
|
||
+
|
||
+ boolean load(PluginProvider<T> provider, T provided);
|
||
+
|
||
+ List<String> requiredDependencies(PluginProvider<T> provider);
|
||
+
|
||
+ List<String> optionalDependencies(PluginProvider<T> provider);
|
||
+
|
||
+ List<String> loadBeforeDependencies(PluginProvider<T> provider);
|
||
+
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..e8d5ce5f137a960e353c6722e1f20f9b342d4ba5
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/ProviderLoadingStrategy.java
|
||
@@ -0,0 +1,16 @@
|
||
+package io.papermc.paper.plugin.entrypoint.strategy;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+
|
||
+import java.util.List;
|
||
+
|
||
+/**
|
||
+ * Used by a {@link io.papermc.paper.plugin.storage.SimpleProviderStorage} to load plugin providers in a certain order.
|
||
+ * <p>
|
||
+ * Returns providers loaded.
|
||
+ * @param <P> provider type
|
||
+ */
|
||
+public interface ProviderLoadingStrategy<P> {
|
||
+
|
||
+ List<P> loadProviders(List<PluginProvider<P>> providers);
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/TopographicGraphSorter.java b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/TopographicGraphSorter.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..0720af0d48b39ca46e7d3aba08d7b359ed053461
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/entrypoint/strategy/TopographicGraphSorter.java
|
||
@@ -0,0 +1,61 @@
|
||
+package io.papermc.paper.plugin.entrypoint.strategy;
|
||
+
|
||
+import com.google.common.graph.Graph;
|
||
+
|
||
+import java.util.ArrayDeque;
|
||
+import java.util.ArrayList;
|
||
+import java.util.Deque;
|
||
+import java.util.HashMap;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+
|
||
+public class TopographicGraphSorter {
|
||
+
|
||
+ // Topographically sort dependencies
|
||
+ public static <N> List<N> sortGraph(Graph<N> graph) throws PluginGraphCycleException {
|
||
+ List<N> sorted = new ArrayList<>();
|
||
+ Deque<N> roots = new ArrayDeque<>();
|
||
+ Map<N, Integer> nonRoots = new HashMap<>();
|
||
+
|
||
+ for (N node : graph.nodes()) {
|
||
+ // Is a node being referred to by any other nodes?
|
||
+ int degree = graph.inDegree(node);
|
||
+ if (degree == 0) {
|
||
+ // Is a root
|
||
+ roots.add(node);
|
||
+ } else {
|
||
+ // Isn't a root, the number represents how many nodes connect to it.
|
||
+ nonRoots.put(node, degree);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // Pick from nodes that aren't referred to anywhere else
|
||
+ while (!roots.isEmpty()) {
|
||
+ N next = roots.remove();
|
||
+
|
||
+ for (N successor : graph.successors(next)) {
|
||
+ // Traverse through, moving down a degree
|
||
+ int newInDegree = nonRoots.get(successor) - 1;
|
||
+
|
||
+ if (newInDegree == 0) {
|
||
+ nonRoots.remove(successor);
|
||
+ roots.add(successor);
|
||
+ } else {
|
||
+ nonRoots.put(successor, newInDegree);
|
||
+ }
|
||
+
|
||
+ }
|
||
+ sorted.add(next);
|
||
+ }
|
||
+
|
||
+ if (!nonRoots.isEmpty()) {
|
||
+ throw new GraphCycleException();
|
||
+ }
|
||
+
|
||
+ return sorted;
|
||
+ }
|
||
+
|
||
+ public static class GraphCycleException extends RuntimeException {
|
||
+
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..d49dd7d17ce7ac17efb19b33de163015787213f7
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/loader/PaperClasspathBuilder.java
|
||
@@ -0,0 +1,64 @@
|
||
+package io.papermc.paper.plugin.loader;
|
||
+
|
||
+import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
|
||
+import io.papermc.paper.plugin.loader.library.ClassPathLibrary;
|
||
+import io.papermc.paper.plugin.loader.library.PaperLibraryStore;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.io.IOException;
|
||
+import java.net.MalformedURLException;
|
||
+import java.net.URL;
|
||
+import java.net.URLClassLoader;
|
||
+import java.nio.file.Path;
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+public class PaperClasspathBuilder implements PluginClasspathBuilder {
|
||
+
|
||
+ private final List<ClassPathLibrary> libraries = new ArrayList<>();
|
||
+
|
||
+ private final PluginProviderContext context;
|
||
+
|
||
+ public PaperClasspathBuilder(PluginProviderContext context) {
|
||
+ this.context = context;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull PluginProviderContext getContext() {
|
||
+ return this.context;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull PluginClasspathBuilder addLibrary(@NotNull ClassPathLibrary classPathLibrary) {
|
||
+ this.libraries.add(classPathLibrary);
|
||
+ return this;
|
||
+ }
|
||
+
|
||
+ public PaperPluginClassLoader buildClassLoader(Logger logger, Path source, JarFile jarFile, PaperPluginMeta configuration) {
|
||
+ PaperLibraryStore paperLibraryStore = new PaperLibraryStore();
|
||
+ for (ClassPathLibrary library : this.libraries) {
|
||
+ library.register(paperLibraryStore);
|
||
+ }
|
||
+
|
||
+ List<Path> paths = paperLibraryStore.getPaths();
|
||
+ URL[] urls = new URL[paths.size()];
|
||
+ for (int i = 0; i < paths.size(); i++) {
|
||
+ Path path = paperLibraryStore.getPaths().get(i);
|
||
+ try {
|
||
+ urls[i] = path.toUri().toURL();
|
||
+ } catch (MalformedURLException e) {
|
||
+ throw new AssertionError(e);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ return new PaperPluginClassLoader(logger, source, jarFile, configuration, this.getClass().getClassLoader(), new URLClassLoader(urls));
|
||
+ } catch (IOException exception) {
|
||
+ throw new RuntimeException(exception);
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/loader/library/PaperLibraryStore.java b/src/main/java/io/papermc/paper/plugin/loader/library/PaperLibraryStore.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..5fcce65009f715d46dd3013f1f92ec8393d66e15
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/loader/library/PaperLibraryStore.java
|
||
@@ -0,0 +1,21 @@
|
||
+package io.papermc.paper.plugin.loader.library;
|
||
+
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.nio.file.Path;
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+
|
||
+public class PaperLibraryStore implements LibraryStore {
|
||
+
|
||
+ private final List<Path> paths = new ArrayList<>();
|
||
+
|
||
+ @Override
|
||
+ public void addLibrary(@NotNull Path library) {
|
||
+ this.paths.add(library);
|
||
+ }
|
||
+
|
||
+ public List<Path> getPaths() {
|
||
+ return this.paths;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..a47c0c1dedf5f8cd7006b170639676429c17d74d
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/MultiRuntimePluginProviderStorage.java
|
||
@@ -0,0 +1,49 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import com.mojang.logging.LogUtils;
|
||
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||
+import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent;
|
||
+import io.papermc.paper.plugin.storage.ServerPluginProviderStorage;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+
|
||
+public class MultiRuntimePluginProviderStorage extends ServerPluginProviderStorage {
|
||
+
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+ private final List<JavaPlugin> provided = new ArrayList<>();
|
||
+
|
||
+ @Override
|
||
+ public void register(PluginProvider<JavaPlugin> provider) {
|
||
+ if (provider instanceof PaperPluginParent.PaperServerPluginProvider) {
|
||
+ LOGGER.warn("Skipping loading of paper plugin requested from SimplePluginManager.");
|
||
+ return;
|
||
+ }
|
||
+ super.register(provider);
|
||
+ /*
|
||
+ Register the provider into the server entrypoint, this allows it to show in /plugins correctly. Generally it might be better in the future to make a separate storage,
|
||
+ as putting it into the entrypoint handlers doesn't make much sense.
|
||
+ */
|
||
+ LaunchEntryPointHandler.INSTANCE.register(Entrypoint.PLUGIN, provider);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void processProvided(JavaPlugin provided) {
|
||
+ super.processProvided(provided);
|
||
+ this.provided.add(provided);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean exitOnCycleDependencies() {
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ public List<JavaPlugin> getLoaded() {
|
||
+ return this.provided;
|
||
+ }
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java b/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..6f6aaab295018017565ba27d6958a1f5c7b69bc8
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/NormalPaperPermissionManager.java
|
||
@@ -0,0 +1,43 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import org.bukkit.permissions.Permissible;
|
||
+import org.bukkit.permissions.Permission;
|
||
+
|
||
+import java.util.HashMap;
|
||
+import java.util.LinkedHashMap;
|
||
+import java.util.LinkedHashSet;
|
||
+import java.util.Map;
|
||
+import java.util.Set;
|
||
+
|
||
+class NormalPaperPermissionManager extends PaperPermissionManager {
|
||
+
|
||
+ private final Map<String, Permission> permissions = new HashMap<>();
|
||
+ private final Map<Boolean, Set<Permission>> defaultPerms = new LinkedHashMap<>();
|
||
+ private final Map<String, Map<Permissible, Boolean>> permSubs = new HashMap<>();
|
||
+ private final Map<Boolean, Map<Permissible, Boolean>> defSubs = new HashMap<>();
|
||
+
|
||
+ public NormalPaperPermissionManager() {
|
||
+ this.defaultPerms().put(true, new LinkedHashSet<>());
|
||
+ this.defaultPerms().put(false, new LinkedHashSet<>());
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<String, Permission> permissions() {
|
||
+ return this.permissions;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<Boolean, Set<Permission>> defaultPerms() {
|
||
+ return this.defaultPerms;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<String, Map<Permissible, Boolean>> permSubs() {
|
||
+ return this.permSubs;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<Boolean, Map<Permissible, Boolean>> defSubs() {
|
||
+ return this.defSubs;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..7ce9ebba8ce304d1f3f21d4f15ee5f3560d7700b
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperEventManager.java
|
||
@@ -0,0 +1,194 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import co.aikar.timings.TimedEventExecutor;
|
||
+import com.destroystokyo.paper.event.server.ServerExceptionEvent;
|
||
+import com.destroystokyo.paper.exception.ServerEventException;
|
||
+import com.google.common.collect.Sets;
|
||
+import org.bukkit.Server;
|
||
+import org.bukkit.Warning;
|
||
+import org.bukkit.event.Event;
|
||
+import org.bukkit.event.EventHandler;
|
||
+import org.bukkit.event.EventPriority;
|
||
+import org.bukkit.event.HandlerList;
|
||
+import org.bukkit.event.Listener;
|
||
+import org.bukkit.plugin.AuthorNagException;
|
||
+import org.bukkit.plugin.EventExecutor;
|
||
+import org.bukkit.plugin.IllegalPluginAccessException;
|
||
+import org.bukkit.plugin.Plugin;
|
||
+import org.bukkit.plugin.RegisteredListener;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.lang.reflect.Method;
|
||
+import java.util.Arrays;
|
||
+import java.util.HashMap;
|
||
+import java.util.HashSet;
|
||
+import java.util.Map;
|
||
+import java.util.Set;
|
||
+import java.util.logging.Level;
|
||
+
|
||
+class PaperEventManager {
|
||
+
|
||
+ private final Server server;
|
||
+
|
||
+ public PaperEventManager(Server server) {
|
||
+ this.server = server;
|
||
+ }
|
||
+
|
||
+ // SimplePluginManager
|
||
+ public void callEvent(@NotNull Event event) {
|
||
+ if (event.isAsynchronous() && this.server.isPrimaryThread()) {
|
||
+ throw new IllegalStateException(event.getEventName() + " may only be triggered asynchronously.");
|
||
+ } else if (!event.isAsynchronous() && !this.server.isPrimaryThread() && !this.server.isStopping()) {
|
||
+ throw new IllegalStateException(event.getEventName() + " may only be triggered synchronously.");
|
||
+ }
|
||
+
|
||
+ HandlerList handlers = event.getHandlers();
|
||
+ RegisteredListener[] listeners = handlers.getRegisteredListeners();
|
||
+
|
||
+ for (RegisteredListener registration : listeners) {
|
||
+ if (!registration.getPlugin().isEnabled()) {
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ registration.callEvent(event);
|
||
+ } catch (AuthorNagException ex) {
|
||
+ Plugin plugin = registration.getPlugin();
|
||
+
|
||
+ if (plugin.isNaggable()) {
|
||
+ plugin.setNaggable(false);
|
||
+
|
||
+ this.server.getLogger().log(Level.SEVERE, String.format(
|
||
+ "Nag author(s): '%s' of '%s' about the following: %s",
|
||
+ plugin.getPluginMeta().getAuthors(),
|
||
+ plugin.getPluginMeta().getDisplayName(),
|
||
+ ex.getMessage()
|
||
+ ));
|
||
+ }
|
||
+ } catch (Throwable ex) {
|
||
+ String msg = "Could not pass event " + event.getEventName() + " to " + registration.getPlugin().getPluginMeta().getDisplayName();
|
||
+ this.server.getLogger().log(Level.SEVERE, msg, ex);
|
||
+ if (!(event instanceof ServerExceptionEvent)) { // We don't want to cause an endless event loop
|
||
+ this.callEvent(new ServerExceptionEvent(new ServerEventException(msg, ex, registration.getPlugin(), registration.getListener(), event)));
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) {
|
||
+ if (!plugin.isEnabled()) {
|
||
+ throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
|
||
+ }
|
||
+
|
||
+ for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry : this.createRegisteredListeners(listener, plugin).entrySet()) {
|
||
+ this.getEventListeners(this.getRegistrationClass(entry.getKey())).registerAll(entry.getValue());
|
||
+ }
|
||
+
|
||
+ }
|
||
+
|
||
+ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin) {
|
||
+ this.registerEvent(event, listener, priority, executor, plugin, false);
|
||
+ }
|
||
+
|
||
+ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin, boolean ignoreCancelled) {
|
||
+ if (!plugin.isEnabled()) {
|
||
+ throw new IllegalPluginAccessException("Plugin attempted to register " + event + " while not enabled");
|
||
+ }
|
||
+
|
||
+ executor = new TimedEventExecutor(executor, plugin, null, event);
|
||
+ this.getEventListeners(event).register(new RegisteredListener(listener, executor, priority, plugin, ignoreCancelled));
|
||
+ }
|
||
+
|
||
+ @NotNull
|
||
+ private HandlerList getEventListeners(@NotNull Class<? extends Event> type) {
|
||
+ try {
|
||
+ Method method = this.getRegistrationClass(type).getDeclaredMethod("getHandlerList");
|
||
+ method.setAccessible(true);
|
||
+ return (HandlerList) method.invoke(null);
|
||
+ } catch (Exception e) {
|
||
+ throw new IllegalPluginAccessException(e.toString());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @NotNull
|
||
+ private Class<? extends Event> getRegistrationClass(@NotNull Class<? extends Event> clazz) {
|
||
+ try {
|
||
+ clazz.getDeclaredMethod("getHandlerList");
|
||
+ return clazz;
|
||
+ } catch (NoSuchMethodException e) {
|
||
+ if (clazz.getSuperclass() != null
|
||
+ && !clazz.getSuperclass().equals(Event.class)
|
||
+ && Event.class.isAssignableFrom(clazz.getSuperclass())) {
|
||
+ return this.getRegistrationClass(clazz.getSuperclass().asSubclass(Event.class));
|
||
+ } else {
|
||
+ throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName() + ". Static getHandlerList method required!");
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // JavaPluginLoader
|
||
+ @NotNull
|
||
+ public Map<Class<? extends Event>, Set<RegisteredListener>> createRegisteredListeners(@NotNull Listener listener, @NotNull final Plugin plugin) {
|
||
+ Map<Class<? extends Event>, Set<RegisteredListener>> ret = new HashMap<>();
|
||
+
|
||
+ Set<Method> methods;
|
||
+ try {
|
||
+ Class<?> listenerClazz = listener.getClass();
|
||
+ methods = Sets.union(
|
||
+ Set.of(listenerClazz.getMethods()),
|
||
+ Set.of(listenerClazz.getDeclaredMethods())
|
||
+ );
|
||
+ } catch (NoClassDefFoundError e) {
|
||
+ plugin.getLogger().severe("Failed to register events for " + listener.getClass() + " because " + e.getMessage() + " does not exist.");
|
||
+ return ret;
|
||
+ }
|
||
+
|
||
+ for (final Method method : methods) {
|
||
+ final EventHandler eh = method.getAnnotation(EventHandler.class);
|
||
+ if (eh == null) continue;
|
||
+ // Do not register bridge or synthetic methods to avoid event duplication
|
||
+ // Fixes SPIGOT-893
|
||
+ if (method.isBridge() || method.isSynthetic()) {
|
||
+ continue;
|
||
+ }
|
||
+ final Class<?> checkClass;
|
||
+ if (method.getParameterTypes().length != 1 || !Event.class.isAssignableFrom(checkClass = method.getParameterTypes()[0])) {
|
||
+ plugin.getLogger().severe(plugin.getPluginMeta().getDisplayName() + " attempted to register an invalid EventHandler method signature \"" + method.toGenericString() + "\" in " + listener.getClass());
|
||
+ continue;
|
||
+ }
|
||
+ final Class<? extends Event> eventClass = checkClass.asSubclass(Event.class);
|
||
+ method.setAccessible(true);
|
||
+ Set<RegisteredListener> eventSet = ret.computeIfAbsent(eventClass, k -> new HashSet<>());
|
||
+
|
||
+ for (Class<?> clazz = eventClass; Event.class.isAssignableFrom(clazz); clazz = clazz.getSuperclass()) {
|
||
+ // This loop checks for extending deprecated events
|
||
+ if (clazz.getAnnotation(Deprecated.class) != null) {
|
||
+ Warning warning = clazz.getAnnotation(Warning.class);
|
||
+ Warning.WarningState warningState = this.server.getWarningState();
|
||
+ if (!warningState.printFor(warning)) {
|
||
+ break;
|
||
+ }
|
||
+ plugin.getLogger().log(
|
||
+ Level.WARNING,
|
||
+ String.format(
|
||
+ "\"%s\" has registered a listener for %s on method \"%s\", but the event is Deprecated. \"%s\"; please notify the authors %s.",
|
||
+ plugin.getPluginMeta().getDisplayName(),
|
||
+ clazz.getName(),
|
||
+ method.toGenericString(),
|
||
+ (warning != null && warning.reason().length() != 0) ? warning.reason() : "Server performance will be affected",
|
||
+ Arrays.toString(plugin.getPluginMeta().getAuthors().toArray())),
|
||
+ warningState == Warning.WarningState.ON ? new AuthorNagException(null) : null);
|
||
+ break;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ EventExecutor executor = new TimedEventExecutor(EventExecutor.create(method, eventClass), plugin, method, eventClass);
|
||
+ eventSet.add(new RegisteredListener(listener, executor, eh.priority(), plugin, eh.ignoreCancelled()));
|
||
+ }
|
||
+ return ret;
|
||
+ }
|
||
+
|
||
+ public void clearEvents() {
|
||
+ HandlerList.unregisterAll();
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..92a69677f21b2c1c035119d8e5a6af63fa19b801
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPermissionManager.java
|
||
@@ -0,0 +1,201 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import com.google.common.collect.ImmutableSet;
|
||
+import io.papermc.paper.plugin.PermissionManager;
|
||
+import org.bukkit.permissions.Permissible;
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.permissions.PermissionDefault;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.util.HashSet;
|
||
+import java.util.List;
|
||
+import java.util.Locale;
|
||
+import java.util.Map;
|
||
+import java.util.Set;
|
||
+import java.util.WeakHashMap;
|
||
+
|
||
+/**
|
||
+ * See
|
||
+ * {@link StupidSPMPermissionManagerWrapper}
|
||
+ */
|
||
+abstract class PaperPermissionManager implements PermissionManager {
|
||
+
|
||
+ public abstract Map<String, Permission> permissions();
|
||
+
|
||
+ public abstract Map<Boolean, Set<Permission>> defaultPerms();
|
||
+
|
||
+ public abstract Map<String, Map<Permissible, Boolean>> permSubs();
|
||
+
|
||
+ public abstract Map<Boolean, Map<Permissible, Boolean>> defSubs();
|
||
+
|
||
+ @Override
|
||
+ @Nullable
|
||
+ public Permission getPermission(@NotNull String name) {
|
||
+ return this.permissions().get(name.toLowerCase(java.util.Locale.ENGLISH));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void addPermission(@NotNull Permission perm) {
|
||
+ this.addPermission(perm, true);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void addPermissions(@NotNull List<Permission> permissions) {
|
||
+ for (Permission permission : permissions) {
|
||
+ this.addPermission(permission, false);
|
||
+ }
|
||
+ this.dirtyPermissibles();
|
||
+ }
|
||
+
|
||
+ // Allow suppressing permission default calculations
|
||
+ private void addPermission(@NotNull Permission perm, boolean dirty) {
|
||
+ String name = perm.getName().toLowerCase(java.util.Locale.ENGLISH);
|
||
+
|
||
+ if (this.permissions().containsKey(name)) {
|
||
+ throw new IllegalArgumentException("The permission " + name + " is already defined!");
|
||
+ }
|
||
+
|
||
+ this.permissions().put(name, perm);
|
||
+ this.calculatePermissionDefault(perm, dirty);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ @NotNull
|
||
+ public Set<Permission> getDefaultPermissions(boolean op) {
|
||
+ return ImmutableSet.copyOf(this.defaultPerms().get(op));
|
||
+ }
|
||
+
|
||
+
|
||
+ @Override
|
||
+ public void removePermission(@NotNull Permission perm) {
|
||
+ this.removePermission(perm.getName());
|
||
+ }
|
||
+
|
||
+
|
||
+ @Override
|
||
+ public void removePermission(@NotNull String name) {
|
||
+ this.permissions().remove(name.toLowerCase(java.util.Locale.ENGLISH));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void recalculatePermissionDefaults(@NotNull Permission perm) {
|
||
+ // we need a null check here because some plugins for some unknown reason pass null into this?
|
||
+ if (perm != null && this.permissions().containsKey(perm.getName().toLowerCase(Locale.ENGLISH))) {
|
||
+ this.defaultPerms().get(true).remove(perm);
|
||
+ this.defaultPerms().get(false).remove(perm);
|
||
+
|
||
+ this.calculatePermissionDefault(perm, true);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ private void calculatePermissionDefault(@NotNull Permission perm, boolean dirty) {
|
||
+ if ((perm.getDefault() == PermissionDefault.OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
|
||
+ this.defaultPerms().get(true).add(perm);
|
||
+ if (dirty) {
|
||
+ this.dirtyPermissibles(true);
|
||
+ }
|
||
+ }
|
||
+ if ((perm.getDefault() == PermissionDefault.NOT_OP) || (perm.getDefault() == PermissionDefault.TRUE)) {
|
||
+ this.defaultPerms().get(false).add(perm);
|
||
+ if (dirty) {
|
||
+ this.dirtyPermissibles(false);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+
|
||
+ @Override
|
||
+ public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
||
+ String name = permission.toLowerCase(java.util.Locale.ENGLISH);
|
||
+ Map<Permissible, Boolean> map = this.permSubs().computeIfAbsent(name, k -> new WeakHashMap<>());
|
||
+
|
||
+ map.put(permissible, true);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
||
+ String name = permission.toLowerCase(java.util.Locale.ENGLISH);
|
||
+ Map<Permissible, Boolean> map = this.permSubs().get(name);
|
||
+
|
||
+ if (map != null) {
|
||
+ map.remove(permissible);
|
||
+
|
||
+ if (map.isEmpty()) {
|
||
+ this.permSubs().remove(name);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ @NotNull
|
||
+ public Set<Permissible> getPermissionSubscriptions(@NotNull String permission) {
|
||
+ String name = permission.toLowerCase(java.util.Locale.ENGLISH);
|
||
+ Map<Permissible, Boolean> map = this.permSubs().get(name);
|
||
+
|
||
+ if (map == null) {
|
||
+ return ImmutableSet.of();
|
||
+ } else {
|
||
+ return ImmutableSet.copyOf(map.keySet());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
||
+ Map<Permissible, Boolean> map = this.defSubs().computeIfAbsent(op, k -> new WeakHashMap<>());
|
||
+
|
||
+ map.put(permissible, true);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
||
+ Map<Permissible, Boolean> map = this.defSubs().get(op);
|
||
+
|
||
+ if (map != null) {
|
||
+ map.remove(permissible);
|
||
+
|
||
+ if (map.isEmpty()) {
|
||
+ this.defSubs().remove(op);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ @NotNull
|
||
+ public Set<Permissible> getDefaultPermSubscriptions(boolean op) {
|
||
+ Map<Permissible, Boolean> map = this.defSubs().get(op);
|
||
+
|
||
+ if (map == null) {
|
||
+ return ImmutableSet.of();
|
||
+ } else {
|
||
+ return ImmutableSet.copyOf(map.keySet());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ @NotNull
|
||
+ public Set<Permission> getPermissions() {
|
||
+ return new HashSet<>(this.permissions().values());
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void clearPermissions() {
|
||
+ this.permissions().clear();
|
||
+ this.defaultPerms().get(true).clear();
|
||
+ this.defaultPerms().get(false).clear();
|
||
+ }
|
||
+
|
||
+
|
||
+ void dirtyPermissibles(boolean op) {
|
||
+ Set<Permissible> permissibles = this.getDefaultPermSubscriptions(op);
|
||
+
|
||
+ for (Permissible p : permissibles) {
|
||
+ p.recalculatePermissions();
|
||
+ }
|
||
+ }
|
||
+
|
||
+ void dirtyPermissibles() {
|
||
+ this.dirtyPermissibles(true);
|
||
+ this.dirtyPermissibles(false);
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..c0e896343c22badd97c774c4ed1daa4e274f5d44
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginInstanceManager.java
|
||
@@ -0,0 +1,304 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import com.google.common.base.Preconditions;
|
||
+import com.google.common.graph.GraphBuilder;
|
||
+import com.google.common.graph.MutableGraph;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.DependencyUtil;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException;
|
||
+import io.papermc.paper.plugin.provider.classloader.ConfiguredPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.provider.source.DirectoryProviderSource;
|
||
+import io.papermc.paper.plugin.provider.source.FileProviderSource;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.Server;
|
||
+import org.bukkit.World;
|
||
+import org.bukkit.command.Command;
|
||
+import org.bukkit.command.CommandMap;
|
||
+import org.bukkit.command.PluginCommandYamlParser;
|
||
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
||
+import org.bukkit.event.HandlerList;
|
||
+import org.bukkit.event.server.PluginDisableEvent;
|
||
+import org.bukkit.event.server.PluginEnableEvent;
|
||
+import org.bukkit.plugin.InvalidDescriptionException;
|
||
+import org.bukkit.plugin.InvalidPluginException;
|
||
+import org.bukkit.plugin.Plugin;
|
||
+import org.bukkit.plugin.PluginDescriptionFile;
|
||
+import org.bukkit.plugin.PluginManager;
|
||
+import org.bukkit.plugin.UnknownDependencyException;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+import org.spongepowered.configurate.serialize.SerializationException;
|
||
+
|
||
+import java.io.IOException;
|
||
+import java.nio.file.Files;
|
||
+import java.nio.file.Path;
|
||
+import java.util.ArrayList;
|
||
+import java.util.HashMap;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+import java.util.logging.Level;
|
||
+
|
||
+@SuppressWarnings("UnstableApiUsage")
|
||
+class PaperPluginInstanceManager {
|
||
+
|
||
+ private static final FileProviderSource FILE_PROVIDER_SOURCE = new FileProviderSource("File '%s'"::formatted);
|
||
+ private static final DirectoryProviderSource DIRECTORY_PROVIDER_SOURCE = new DirectoryProviderSource();
|
||
+
|
||
+ private final List<Plugin> plugins = new ArrayList<>();
|
||
+ private final Map<String, Plugin> lookupNames = new HashMap<>();
|
||
+
|
||
+ private final PluginManager pluginManager;
|
||
+ private final CommandMap commandMap;
|
||
+ private final Server server;
|
||
+
|
||
+ private final MutableGraph<String> dependencyGraph = GraphBuilder.directed().build();
|
||
+ private final DependencyContext context = new GraphDependencyContext(this.dependencyGraph);
|
||
+
|
||
+ public PaperPluginInstanceManager(PluginManager pluginManager, CommandMap commandMap, Server server) {
|
||
+ this.commandMap = commandMap;
|
||
+ this.server = server;
|
||
+ this.pluginManager = pluginManager;
|
||
+ }
|
||
+
|
||
+ public @Nullable Plugin getPlugin(@NotNull String name) {
|
||
+ return this.lookupNames.get(name.replace(' ', '_').toLowerCase(java.util.Locale.ENGLISH)); // Paper
|
||
+ }
|
||
+
|
||
+ public @NotNull Plugin[] getPlugins() {
|
||
+ return this.plugins.toArray(new Plugin[0]);
|
||
+ }
|
||
+
|
||
+ public boolean isPluginEnabled(@NotNull String name) {
|
||
+ Plugin plugin = this.getPlugin(name);
|
||
+
|
||
+ return this.isPluginEnabled(plugin);
|
||
+ }
|
||
+
|
||
+ public synchronized boolean isPluginEnabled(@Nullable Plugin plugin) {
|
||
+ if ((plugin != null) && (this.plugins.contains(plugin))) {
|
||
+ return plugin.isEnabled();
|
||
+ } else {
|
||
+ return false;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public void loadPlugin(Plugin provided) {
|
||
+ PluginMeta configuration = provided.getPluginMeta();
|
||
+
|
||
+ this.plugins.add(provided);
|
||
+ this.lookupNames.put(configuration.getName().toLowerCase(java.util.Locale.ENGLISH), provided);
|
||
+ for (String providedPlugin : configuration.getProvidedPlugins()) {
|
||
+ this.lookupNames.putIfAbsent(providedPlugin.toLowerCase(java.util.Locale.ENGLISH), provided);
|
||
+ }
|
||
+
|
||
+ DependencyUtil.buildDependencyGraph(this.dependencyGraph, configuration);
|
||
+ }
|
||
+
|
||
+ // InvalidDescriptionException is never used, because the old JavaPluginLoader would wrap the exception.
|
||
+ public @Nullable Plugin loadPlugin(@NotNull Path path) throws InvalidPluginException, UnknownDependencyException {
|
||
+ RuntimePluginEntrypointHandler<SingularRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new SingularRuntimePluginProviderStorage());
|
||
+
|
||
+ try {
|
||
+ FILE_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, path);
|
||
+ } catch (IllegalArgumentException exception) {
|
||
+ return null; // Return null when the plugin file is not valid / plugin type is unknown
|
||
+ } catch (PluginGraphCycleException exception) {
|
||
+ throw new InvalidPluginException("Cannot import plugin that causes cyclic dependencies!");
|
||
+ } catch (SerializationException |
|
||
+ InvalidDescriptionException ex) { // The spigot implementation wraps it in an invalid plugin exception
|
||
+ throw new InvalidPluginException(ex);
|
||
+ } catch (Exception e) {
|
||
+ throw new InvalidPluginException(e);
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ runtimePluginEntrypointHandler.enter(Entrypoint.PLUGIN);
|
||
+ } catch (Throwable e) {
|
||
+ throw new InvalidPluginException(e);
|
||
+ }
|
||
+
|
||
+ return runtimePluginEntrypointHandler.getPluginProviderStorage().getSingleLoaded()
|
||
+ .orElseThrow(() -> new InvalidPluginException("Plugin didn't load any plugin providers?"));
|
||
+ }
|
||
+
|
||
+ // The behavior of this is that all errors are logged instead of being thrown
|
||
+ public @NotNull Plugin[] loadPlugins(@NotNull Path directory) {
|
||
+ Preconditions.checkArgument(Files.isDirectory(directory), "Directory must be a directory"); // Avoid creating a directory if it doesn't exist
|
||
+
|
||
+ RuntimePluginEntrypointHandler<MultiRuntimePluginProviderStorage> runtimePluginEntrypointHandler = new RuntimePluginEntrypointHandler<>(new MultiRuntimePluginProviderStorage());
|
||
+ try {
|
||
+ DIRECTORY_PROVIDER_SOURCE.registerProviders(runtimePluginEntrypointHandler, directory);
|
||
+ runtimePluginEntrypointHandler.enter(Entrypoint.PLUGIN);
|
||
+ } catch (Exception e) {
|
||
+ // This should never happen, any errors that occur in this provider should instead be logged.
|
||
+ this.server.getLogger().log(Level.SEVERE, "Unknown error occurred while loading plugins through PluginManager.", e);
|
||
+ }
|
||
+
|
||
+ return runtimePluginEntrypointHandler.getPluginProviderStorage().getLoaded().toArray(new JavaPlugin[0]);
|
||
+ }
|
||
+
|
||
+ // Plugins are disabled in order like this inorder to "rougly" prevent
|
||
+ // their dependencies unloading first. But, eh.
|
||
+ public void disablePlugins() {
|
||
+ Plugin[] plugins = this.getPlugins();
|
||
+ for (int i = plugins.length - 1; i >= 0; i--) {
|
||
+ this.disablePlugin(plugins[i]);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public void clearPlugins() {
|
||
+ synchronized (this) {
|
||
+ this.disablePlugins();
|
||
+ this.plugins.clear();
|
||
+ this.lookupNames.clear();
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public synchronized void enablePlugin(@NotNull Plugin plugin) {
|
||
+ if (plugin.isEnabled()) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ if (plugin.getPluginMeta() instanceof PluginDescriptionFile) {
|
||
+ List<Command> bukkitCommands = PluginCommandYamlParser.parse(plugin);
|
||
+
|
||
+ if (!bukkitCommands.isEmpty()) {
|
||
+ this.commandMap.registerAll(plugin.getPluginMeta().getName(), bukkitCommands);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ String enableMsg = "Enabling " + plugin.getPluginMeta().getDisplayName();
|
||
+ if (plugin.getPluginMeta() instanceof PluginDescriptionFile descriptionFile && CraftMagicNumbers.isLegacy(descriptionFile)) {
|
||
+ enableMsg += "*";
|
||
+ }
|
||
+ plugin.getLogger().info(enableMsg);
|
||
+
|
||
+ JavaPlugin jPlugin = (JavaPlugin) plugin;
|
||
+
|
||
+ if (jPlugin.getClass().getClassLoader() instanceof ConfiguredPluginClassLoader classLoader) { // Paper
|
||
+ if (PaperClassLoaderStorage.instance().registerUnsafePlugin(classLoader)) {
|
||
+ this.server.getLogger().log(Level.WARNING, "Enabled plugin with unregistered ConfiguredPluginClassLoader " + plugin.getPluginMeta().getDisplayName());
|
||
+ }
|
||
+ } // Paper
|
||
+
|
||
+ try {
|
||
+ jPlugin.setEnabled(true);
|
||
+ } catch (Throwable ex) {
|
||
+ this.server.getLogger().log(Level.SEVERE, "Error occurred while enabling " + plugin.getPluginMeta().getDisplayName() + " (Is it up to date?)", ex);
|
||
+ // Paper start - Disable plugins that fail to load
|
||
+ this.server.getPluginManager().disablePlugin(jPlugin);
|
||
+ return;
|
||
+ // Paper end
|
||
+ }
|
||
+
|
||
+ // Perhaps abort here, rather than continue going, but as it stands,
|
||
+ // an abort is not possible the way it's currently written
|
||
+ this.server.getPluginManager().callEvent(new PluginEnableEvent(plugin));
|
||
+ } catch (Throwable ex) {
|
||
+ this.handlePluginException("Error occurred (in the plugin loader) while enabling "
|
||
+ + plugin.getPluginMeta().getDisplayName() + " (Is it up to date?)", ex, plugin);
|
||
+ }
|
||
+
|
||
+ HandlerList.bakeAll();
|
||
+ }
|
||
+
|
||
+ public synchronized void disablePlugin(@NotNull Plugin plugin) {
|
||
+ if (!(plugin instanceof JavaPlugin javaPlugin)) {
|
||
+ throw new IllegalArgumentException("Only expects java plugins.");
|
||
+ }
|
||
+ if (!plugin.isEnabled()) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ String pluginName = plugin.getPluginMeta().getDisplayName();
|
||
+
|
||
+ try {
|
||
+ plugin.getLogger().info("Disabling %s".formatted(pluginName));
|
||
+
|
||
+ this.server.getPluginManager().callEvent(new PluginDisableEvent(plugin));
|
||
+
|
||
+ javaPlugin.setEnabled(false);
|
||
+
|
||
+ ClassLoader classLoader = plugin.getClass().getClassLoader();
|
||
+ if (classLoader instanceof ConfiguredPluginClassLoader configuredPluginClassLoader) {
|
||
+ try {
|
||
+ configuredPluginClassLoader.close();
|
||
+ } catch (IOException ex) {
|
||
+ this.server.getLogger().log(Level.WARNING, "Error closing the classloader for '" + pluginName + "'", ex); // Paper - log exception
|
||
+ }
|
||
+ // Remove from the classloader pool inorder to prevent plugins from trying
|
||
+ // to access classes
|
||
+ PaperClassLoaderStorage.instance().unregisterClassloader(configuredPluginClassLoader);
|
||
+ }
|
||
+
|
||
+ } catch (Throwable ex) {
|
||
+ this.handlePluginException("Error occurred (in the plugin loader) while disabling "
|
||
+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ this.server.getScheduler().cancelTasks(plugin);
|
||
+ } catch (Throwable ex) {
|
||
+ this.handlePluginException("Error occurred (in the plugin loader) while cancelling tasks for "
|
||
+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ this.server.getServicesManager().unregisterAll(plugin);
|
||
+ } catch (Throwable ex) {
|
||
+ this.handlePluginException("Error occurred (in the plugin loader) while unregistering services for "
|
||
+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ HandlerList.unregisterAll(plugin);
|
||
+ } catch (Throwable ex) {
|
||
+ this.handlePluginException("Error occurred (in the plugin loader) while unregistering events for "
|
||
+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ this.server.getMessenger().unregisterIncomingPluginChannel(plugin);
|
||
+ this.server.getMessenger().unregisterOutgoingPluginChannel(plugin);
|
||
+ } catch (Throwable ex) {
|
||
+ this.handlePluginException("Error occurred (in the plugin loader) while unregistering plugin channels for "
|
||
+ + pluginName + " (Is it up to date?)", ex, plugin); // Paper
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ for (World world : this.server.getWorlds()) {
|
||
+ world.removePluginChunkTickets(plugin);
|
||
+ }
|
||
+ } catch (Throwable ex) {
|
||
+ this.handlePluginException("Error occurred (in the plugin loader) while removing chunk tickets for " + pluginName + " (Is it up to date?)", ex, plugin); // Paper
|
||
+ }
|
||
+
|
||
+ }
|
||
+
|
||
+ // TODO: Implement event part in future patch (paper patch move up, this patch is lower)
|
||
+ private void handlePluginException(String msg, Throwable ex, Plugin plugin) {
|
||
+ Bukkit.getServer().getLogger().log(Level.SEVERE, msg, ex);
|
||
+ this.pluginManager.callEvent(new com.destroystokyo.paper.event.server.ServerExceptionEvent(new com.destroystokyo.paper.exception.ServerPluginEnableDisableException(msg, ex, plugin)));
|
||
+ }
|
||
+
|
||
+ public boolean isTransitiveDepend(@NotNull PluginMeta plugin, @NotNull PluginMeta depend) {
|
||
+ return this.context.isTransitiveDependency(plugin, depend);
|
||
+ }
|
||
+
|
||
+ public boolean hasDependency(String pluginIdentifier) {
|
||
+ return this.getPlugin(pluginIdentifier) != null;
|
||
+ }
|
||
+
|
||
+ // Debug only
|
||
+ @ApiStatus.Internal
|
||
+ public MutableGraph<String> getDependencyGraph() {
|
||
+ return this.dependencyGraph;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..cdc3f6c5c845fe00478c41a22d39d7b348673559
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/PaperPluginManagerImpl.java
|
||
@@ -0,0 +1,241 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import com.google.common.graph.MutableGraph;
|
||
+import io.papermc.paper.plugin.PermissionManager;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.Server;
|
||
+import org.bukkit.command.CommandMap;
|
||
+import org.bukkit.craftbukkit.CraftServer;
|
||
+import org.bukkit.event.Event;
|
||
+import org.bukkit.event.EventPriority;
|
||
+import org.bukkit.event.Listener;
|
||
+import org.bukkit.permissions.Permissible;
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.plugin.EventExecutor;
|
||
+import org.bukkit.plugin.InvalidDescriptionException;
|
||
+import org.bukkit.plugin.InvalidPluginException;
|
||
+import org.bukkit.plugin.Plugin;
|
||
+import org.bukkit.plugin.PluginLoader;
|
||
+import org.bukkit.plugin.PluginManager;
|
||
+import org.bukkit.plugin.SimplePluginManager;
|
||
+import org.bukkit.plugin.UnknownDependencyException;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.io.File;
|
||
+import java.util.List;
|
||
+import java.util.Set;
|
||
+
|
||
+public class PaperPluginManagerImpl implements PluginManager, DependencyContext {
|
||
+
|
||
+ private final PaperPluginInstanceManager instanceManager;
|
||
+ private final PaperEventManager paperEventManager;
|
||
+ private PermissionManager permissionManager;
|
||
+
|
||
+ public PaperPluginManagerImpl(Server server, CommandMap commandMap, @Nullable SimplePluginManager permissionManager) {
|
||
+ this.instanceManager = new PaperPluginInstanceManager(this, commandMap, server);
|
||
+ this.paperEventManager = new PaperEventManager(server);
|
||
+
|
||
+ if (permissionManager == null) {
|
||
+ this.permissionManager = new NormalPaperPermissionManager();
|
||
+ } else {
|
||
+ this.permissionManager = new StupidSPMPermissionManagerWrapper(permissionManager); // TODO: See comment when SimplePermissionManager is removed
|
||
+ }
|
||
+ }
|
||
+
|
||
+ // REMOVE THIS WHEN SimplePluginManager is removed.
|
||
+ // Just cast and use Bukkit.getServer().getPluginManager()
|
||
+ public static PaperPluginManagerImpl getInstance() {
|
||
+ return ((CraftServer) (Bukkit.getServer())).paperPluginManager;
|
||
+ }
|
||
+
|
||
+ // Plugin Manipulation
|
||
+
|
||
+ @Override
|
||
+ public @Nullable Plugin getPlugin(@NotNull String name) {
|
||
+ return this.instanceManager.getPlugin(name);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Plugin[] getPlugins() {
|
||
+ return this.instanceManager.getPlugins();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean isPluginEnabled(@NotNull String name) {
|
||
+ return this.instanceManager.isPluginEnabled(name);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean isPluginEnabled(@Nullable Plugin plugin) {
|
||
+ return this.instanceManager.isPluginEnabled(plugin);
|
||
+ }
|
||
+
|
||
+ public void loadPlugin(Plugin plugin) {
|
||
+ this.instanceManager.loadPlugin(plugin);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable Plugin loadPlugin(@NotNull File file) throws InvalidPluginException, InvalidDescriptionException, UnknownDependencyException {
|
||
+ return this.instanceManager.loadPlugin(file.toPath());
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Plugin[] loadPlugins(@NotNull File directory) {
|
||
+ return this.instanceManager.loadPlugins(directory.toPath());
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void disablePlugins() {
|
||
+ this.instanceManager.disablePlugins();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public synchronized void clearPlugins() {
|
||
+ this.instanceManager.clearPlugins();
|
||
+ this.permissionManager.clearPermissions();
|
||
+ this.paperEventManager.clearEvents();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void enablePlugin(@NotNull Plugin plugin) {
|
||
+ this.instanceManager.enablePlugin(plugin);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void disablePlugin(@NotNull Plugin plugin) {
|
||
+ this.instanceManager.disablePlugin(plugin);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean isTransitiveDependency(PluginMeta pluginMeta, PluginMeta dependencyConfig) {
|
||
+ return this.instanceManager.isTransitiveDepend(pluginMeta, dependencyConfig);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean hasDependency(String pluginIdentifier) {
|
||
+ return this.instanceManager.hasDependency(pluginIdentifier);
|
||
+ }
|
||
+
|
||
+ // Event manipulation
|
||
+
|
||
+ @Override
|
||
+ public void callEvent(@NotNull Event event) throws IllegalStateException {
|
||
+ this.paperEventManager.callEvent(event);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void registerEvents(@NotNull Listener listener, @NotNull Plugin plugin) {
|
||
+ this.paperEventManager.registerEvents(listener, plugin);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin) {
|
||
+ this.paperEventManager.registerEvent(event, listener, priority, executor, plugin);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void registerEvent(@NotNull Class<? extends Event> event, @NotNull Listener listener, @NotNull EventPriority priority, @NotNull EventExecutor executor, @NotNull Plugin plugin, boolean ignoreCancelled) {
|
||
+ this.paperEventManager.registerEvent(event, listener, priority, executor, plugin, ignoreCancelled);
|
||
+ }
|
||
+
|
||
+ // Permission manipulation
|
||
+
|
||
+ @Override
|
||
+ public @Nullable Permission getPermission(@NotNull String name) {
|
||
+ return this.permissionManager.getPermission(name);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void addPermission(@NotNull Permission perm) {
|
||
+ this.permissionManager.addPermission(perm);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void removePermission(@NotNull Permission perm) {
|
||
+ this.permissionManager.removePermission(perm);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void removePermission(@NotNull String name) {
|
||
+ this.permissionManager.removePermission(name);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Set<Permission> getDefaultPermissions(boolean op) {
|
||
+ return this.permissionManager.getDefaultPermissions(op);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void recalculatePermissionDefaults(@NotNull Permission perm) {
|
||
+ this.permissionManager.recalculatePermissionDefaults(perm);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void subscribeToPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
||
+ this.permissionManager.subscribeToPermission(permission, permissible);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void unsubscribeFromPermission(@NotNull String permission, @NotNull Permissible permissible) {
|
||
+ this.permissionManager.unsubscribeFromPermission(permission, permissible);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Set<Permissible> getPermissionSubscriptions(@NotNull String permission) {
|
||
+ return this.permissionManager.getPermissionSubscriptions(permission);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void subscribeToDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
||
+ this.permissionManager.subscribeToDefaultPerms(op, permissible);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void unsubscribeFromDefaultPerms(boolean op, @NotNull Permissible permissible) {
|
||
+ this.permissionManager.unsubscribeFromDefaultPerms(op, permissible);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Set<Permissible> getDefaultPermSubscriptions(boolean op) {
|
||
+ return this.permissionManager.getDefaultPermSubscriptions(op);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Set<Permission> getPermissions() {
|
||
+ return this.permissionManager.getPermissions();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void addPermissions(@NotNull List<Permission> perm) {
|
||
+ this.permissionManager.addPermissions(perm);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void clearPermissions() {
|
||
+ this.permissionManager.clearPermissions();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void overridePermissionManager(@NotNull Plugin plugin, @Nullable PermissionManager permissionManager) {
|
||
+ this.permissionManager = permissionManager;
|
||
+ }
|
||
+
|
||
+ // Etc
|
||
+
|
||
+ @Override
|
||
+ public boolean useTimings() {
|
||
+ return co.aikar.timings.Timings.isTimingsEnabled();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void registerInterface(@NotNull Class<? extends PluginLoader> loader) throws IllegalArgumentException {
|
||
+ throw new UnsupportedOperationException();
|
||
+ }
|
||
+
|
||
+ public MutableGraph<String> getInstanceManagerGraph() {
|
||
+ return instanceManager.getDependencyGraph();
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/RuntimePluginEntrypointHandler.java b/src/main/java/io/papermc/paper/plugin/manager/RuntimePluginEntrypointHandler.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..5d50d1d312388e979c0e1cd53a6bf5977ca6e549
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/RuntimePluginEntrypointHandler.java
|
||
@@ -0,0 +1,47 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import com.destroystokyo.paper.util.SneakyThrow;
|
||
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||
+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.storage.ProviderStorage;
|
||
+import org.bukkit.plugin.InvalidPluginException;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+/**
|
||
+ * Used for loading plugins during runtime, only supporting providers that are plugins.
|
||
+ * This is only used for the plugin manager, as it only allows plugins to be
|
||
+ * registered to a provider storage.
|
||
+ */
|
||
+class RuntimePluginEntrypointHandler<T extends ProviderStorage<JavaPlugin>> implements EntrypointHandler {
|
||
+
|
||
+ private final T providerStorage;
|
||
+
|
||
+ RuntimePluginEntrypointHandler(T providerStorage) {
|
||
+ this.providerStorage = providerStorage;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public <T> void register(Entrypoint<T> entrypoint, PluginProvider<T> provider) {
|
||
+ if (!entrypoint.equals(Entrypoint.PLUGIN)) {
|
||
+ SneakyThrow.sneaky(new InvalidPluginException("Plugin cannot register entrypoints other than PLUGIN during runtime. Tried registering %s!".formatted(entrypoint)));
|
||
+ // We have to throw an invalid plugin exception for legacy reasons
|
||
+ }
|
||
+
|
||
+ this.providerStorage.register((PluginProvider<JavaPlugin>) provider);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void enter(Entrypoint<?> entrypoint) {
|
||
+ if (entrypoint != Entrypoint.PLUGIN) {
|
||
+ throw new IllegalArgumentException("Only plugin entrypoint supported");
|
||
+ }
|
||
+ this.providerStorage.enter();
|
||
+ }
|
||
+
|
||
+ @NotNull
|
||
+ public T getPluginProviderStorage() {
|
||
+ return this.providerStorage;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..ebc01cbffdc55ca9664897ff10ef14ad6c5ab726
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/SingularRuntimePluginProviderStorage.java
|
||
@@ -0,0 +1,80 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import com.destroystokyo.paper.util.SneakyThrow;
|
||
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||
+import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent;
|
||
+import io.papermc.paper.plugin.storage.ServerPluginProviderStorage;
|
||
+import org.bukkit.plugin.InvalidPluginException;
|
||
+import org.bukkit.plugin.PluginDescriptionFile;
|
||
+import org.bukkit.plugin.UnknownDependencyException;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+import java.util.Optional;
|
||
+
|
||
+/**
|
||
+ * Used for registering a single plugin provider.
|
||
+ * This has special behavior in that some errors are thrown instead of logged.
|
||
+ */
|
||
+class SingularRuntimePluginProviderStorage extends ServerPluginProviderStorage {
|
||
+
|
||
+ private PluginProvider<JavaPlugin> lastProvider;
|
||
+ private JavaPlugin singleLoaded;
|
||
+
|
||
+ @Override
|
||
+ public void register(PluginProvider<JavaPlugin> provider) {
|
||
+ super.register(provider);
|
||
+ if (this.lastProvider != null) {
|
||
+ SneakyThrow.sneaky(new InvalidPluginException("Plugin registered two JavaPlugins"));
|
||
+ }
|
||
+ if (provider instanceof PaperPluginParent.PaperServerPluginProvider) {
|
||
+ throw new IllegalStateException("Cannot register paper plugins during runtime!");
|
||
+ }
|
||
+ this.lastProvider = provider;
|
||
+ // Register the provider into the server entrypoint, this allows it to show in /plugins correctly.
|
||
+ // Generally it might be better in the future to make a separate storage, as putting it into the entrypoint handlers doesn't make much sense.
|
||
+ LaunchEntryPointHandler.INSTANCE.register(Entrypoint.PLUGIN, provider);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void enter() {
|
||
+ PluginProvider<JavaPlugin> provider = this.lastProvider;
|
||
+ if (provider == null) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ // Manually validate dependencies, LEGACY BEHAVIOR.
|
||
+ // Normally it is logged, but manually adding one plugin will cause it to actually throw exceptions.
|
||
+ PluginDescriptionFile descriptionFile = (PluginDescriptionFile) provider.getMeta();
|
||
+ List<String> missingDependencies = new ArrayList<>();
|
||
+ for (String dependency : descriptionFile.getDepend()) {
|
||
+ if (!PaperPluginManagerImpl.getInstance().isPluginEnabled(dependency)) {
|
||
+ missingDependencies.add(dependency);
|
||
+ }
|
||
+ }
|
||
+ if (!missingDependencies.isEmpty()) {
|
||
+ throw new UnknownDependencyException(missingDependencies, provider.getFileName().toString());
|
||
+ }
|
||
+
|
||
+ // Go through normal plugin loading logic
|
||
+ super.enter();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void processProvided(JavaPlugin provided) {
|
||
+ super.processProvided(provided);
|
||
+ this.singleLoaded = provided;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean exitOnCycleDependencies() {
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ public Optional<JavaPlugin> getSingleLoaded() {
|
||
+ return Optional.ofNullable(this.singleLoaded);
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java b/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..ea8cf22c35242eb9f3914b95df00e20504aef5c1
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/manager/StupidSPMPermissionManagerWrapper.java
|
||
@@ -0,0 +1,42 @@
|
||
+package io.papermc.paper.plugin.manager;
|
||
+
|
||
+import org.bukkit.permissions.Permissible;
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.plugin.SimplePluginManager;
|
||
+
|
||
+import java.util.Map;
|
||
+import java.util.Set;
|
||
+
|
||
+/*
|
||
+This is actually so cursed I hate it.
|
||
+We need to wrap these in fields as people override the fields, so we need to access them lazily at all times.
|
||
+// TODO: When SimplePluginManager is GONE remove this and cleanup the PaperPermissionManager to use actual fields.
|
||
+ */
|
||
+class StupidSPMPermissionManagerWrapper extends PaperPermissionManager {
|
||
+
|
||
+ private final SimplePluginManager simplePluginManager;
|
||
+
|
||
+ public StupidSPMPermissionManagerWrapper(SimplePluginManager simplePluginManager) {
|
||
+ this.simplePluginManager = simplePluginManager;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<String, Permission> permissions() {
|
||
+ return this.simplePluginManager.permissions;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<Boolean, Set<Permission>> defaultPerms() {
|
||
+ return this.simplePluginManager.defaultPerms;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<String, Map<Permissible, Boolean>> permSubs() {
|
||
+ return this.simplePluginManager.permSubs;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Map<Boolean, Map<Permissible, Boolean>> defSubs() {
|
||
+ return this.simplePluginManager.defSubs;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java b/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..11b6cb377c9b04b63b6359918eef214ba3032d96
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/PluginProvider.java
|
||
@@ -0,0 +1,47 @@
|
||
+package io.papermc.paper.plugin.provider;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.nio.file.Path;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+/**
|
||
+ * PluginProviders are created by a {@link io.papermc.paper.plugin.provider.source.ProviderSource},
|
||
+ * which is loaded into an {@link io.papermc.paper.plugin.entrypoint.EntrypointHandler}.
|
||
+ * <p>
|
||
+ * A PluginProvider is responsible for providing part of a plugin, whether it's a Bootstrapper or Server Plugin.
|
||
+ * The point of this class is to be able to create the actual instance later, as at the time this is created the server
|
||
+ * may be missing some key parts. For example, the Bukkit singleton will not be initialized yet, therefor we need to
|
||
+ * have a PluginServerProvider load the server plugin later.
|
||
+ * <p>
|
||
+ * Plugin providers are currently not exposed in any way of the api. It is preferred that this stays this way,
|
||
+ * as providers are only needed for initialization.
|
||
+ *
|
||
+ * @param <T> provider type
|
||
+ */
|
||
+@ApiStatus.Internal
|
||
+public interface PluginProvider<T> {
|
||
+
|
||
+ @NotNull
|
||
+ Path getSource();
|
||
+
|
||
+ default Path getFileName() {
|
||
+ return this.getSource().getFileName();
|
||
+ }
|
||
+
|
||
+ default Path getParentSource() {
|
||
+ return this.getSource().getParent();
|
||
+ }
|
||
+
|
||
+ JarFile file();
|
||
+
|
||
+ T createInstance();
|
||
+
|
||
+ PluginMeta getMeta();
|
||
+
|
||
+ Logger getLogger();
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..6154e864b0ff01cb70acaaeee5ca8c9f4a90a90e
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatus.java
|
||
@@ -0,0 +1,13 @@
|
||
+package io.papermc.paper.plugin.provider;
|
||
+
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+
|
||
+/**
|
||
+ * This is used for the /plugins command, where it will look in the {@link io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler} and
|
||
+ * use the provider statuses to determine the color.
|
||
+ */
|
||
+@ApiStatus.Internal
|
||
+public enum ProviderStatus {
|
||
+ INITIALIZED,
|
||
+ ERRORED,
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/ProviderStatusHolder.java b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatusHolder.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..17284d0f61c459dff765c0adae4ad2c641e054c1
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/ProviderStatusHolder.java
|
||
@@ -0,0 +1,11 @@
|
||
+package io.papermc.paper.plugin.provider;
|
||
+
|
||
+/**
|
||
+ * This is used to mark that a plugin provider is able to hold a status for the /plugins command.
|
||
+ */
|
||
+public interface ProviderStatusHolder {
|
||
+
|
||
+ ProviderStatus getLastProvidedStatus();
|
||
+
|
||
+ void setStatus(ProviderStatus status);
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/FlattenedResolver.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/FlattenedResolver.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..6ba3bcc468c0a60c76d6d0f0243bda661c737f2f
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/FlattenedResolver.java
|
||
@@ -0,0 +1,29 @@
|
||
+package io.papermc.paper.plugin.provider.configuration;
|
||
+
|
||
+import org.checkerframework.checker.nullness.qual.Nullable;
|
||
+import org.spongepowered.configurate.objectmapping.meta.NodeResolver;
|
||
+
|
||
+import java.lang.annotation.ElementType;
|
||
+import java.lang.annotation.Retention;
|
||
+import java.lang.annotation.RetentionPolicy;
|
||
+import java.lang.annotation.Target;
|
||
+import java.lang.reflect.AnnotatedElement;
|
||
+
|
||
+@Retention(RetentionPolicy.RUNTIME)
|
||
+@Target(ElementType.FIELD)
|
||
+public @interface FlattenedResolver {
|
||
+
|
||
+ final class Factory implements NodeResolver.Factory {
|
||
+
|
||
+ @Override
|
||
+ public @Nullable NodeResolver make(String name, AnnotatedElement element) {
|
||
+ if (element.isAnnotationPresent(FlattenedResolver.class)) {
|
||
+ return (node) -> node;
|
||
+ } else {
|
||
+ return null;
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..622a6c5fdfcb6d8cc12054f3de81c73c9af2389f
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/PaperPluginMeta.java
|
||
@@ -0,0 +1,213 @@
|
||
+package io.papermc.paper.plugin.provider.configuration;
|
||
+
|
||
+import com.google.common.collect.ImmutableList;
|
||
+import io.leangen.geantyref.TypeToken;
|
||
+import io.papermc.paper.configuration.constraint.Constraint;
|
||
+import io.papermc.paper.configuration.serializer.ComponentSerializer;
|
||
+import io.papermc.paper.configuration.serializer.EnumValueSerializer;
|
||
+import io.papermc.paper.configuration.serializer.collections.MapSerializer;
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.provider.configuration.serializer.ImmutableListSerializer;
|
||
+import io.papermc.paper.plugin.provider.configuration.serializer.PermissionConfigurationSerializer;
|
||
+import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
|
||
+import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration;
|
||
+import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration;
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.permissions.PermissionDefault;
|
||
+import org.bukkit.plugin.PluginLoadOrder;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+import org.spongepowered.configurate.CommentedConfigurationNode;
|
||
+import org.spongepowered.configurate.ConfigurateException;
|
||
+import org.spongepowered.configurate.loader.HeaderMode;
|
||
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||
+import org.spongepowered.configurate.objectmapping.ObjectMapper;
|
||
+import org.spongepowered.configurate.objectmapping.meta.Required;
|
||
+import org.spongepowered.configurate.yaml.NodeStyle;
|
||
+import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
|
||
+
|
||
+import java.io.BufferedReader;
|
||
+import java.util.List;
|
||
+
|
||
+@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
|
||
+@ConfigSerializable
|
||
+public class PaperPluginMeta implements PluginMeta {
|
||
+
|
||
+ @PluginConfigConstraints.PluginName
|
||
+ @Required
|
||
+ private String name;
|
||
+ @Required
|
||
+ @PluginConfigConstraints.PluginNameSpace
|
||
+ private String main;
|
||
+ @PluginConfigConstraints.PluginNameSpace
|
||
+ private String bootstrapper;
|
||
+ @PluginConfigConstraints.PluginNameSpace
|
||
+ private String loader;
|
||
+ private List<DependencyConfiguration> dependencies = List.of();
|
||
+ private List<String> loadBefore = List.of();
|
||
+ private List<String> provides = List.of();
|
||
+ private boolean hasOpenClassloader = false;
|
||
+ @Required
|
||
+ private String version;
|
||
+ private String description;
|
||
+ private List<String> authors = List.of();
|
||
+ private List<String> contributors = List.of();
|
||
+ private String website;
|
||
+ private String prefix;
|
||
+ private PluginLoadOrder load = PluginLoadOrder.POSTWORLD;
|
||
+ @FlattenedResolver
|
||
+ private PermissionConfiguration permissionConfiguration = new PermissionConfiguration(PermissionDefault.OP, List.of());
|
||
+ @Required
|
||
+ @PluginConfigConstraints.PluginVersion
|
||
+ private String apiVersion;
|
||
+
|
||
+ private transient String displayName;
|
||
+
|
||
+ public PaperPluginMeta() {
|
||
+ }
|
||
+
|
||
+ public static PaperPluginMeta create(BufferedReader reader) throws ConfigurateException {
|
||
+ YamlConfigurationLoader loader = YamlConfigurationLoader.builder()
|
||
+ .indent(2)
|
||
+ .nodeStyle(NodeStyle.BLOCK)
|
||
+ .headerMode(HeaderMode.NONE)
|
||
+ .source(() -> reader)
|
||
+ .defaultOptions((options) -> {
|
||
+
|
||
+ return options.serializers((serializers) -> {
|
||
+ serializers
|
||
+ .register(new EnumValueSerializer())
|
||
+ .register(MapSerializer.TYPE, new MapSerializer(false))
|
||
+ .register(new TypeToken<>() {
|
||
+ }, new ImmutableListSerializer())
|
||
+ .register(PermissionConfiguration.class, PermissionConfigurationSerializer.SERIALIZER)
|
||
+ .register(new ComponentSerializer())
|
||
+ .registerAnnotatedObjects(
|
||
+ ObjectMapper.factoryBuilder()
|
||
+ .addConstraint(Constraint.class, new Constraint.Factory())
|
||
+ .addConstraint(PluginConfigConstraints.PluginName.class, String.class, new PluginConfigConstraints.PluginName.Factory())
|
||
+ .addConstraint(PluginConfigConstraints.PluginVersion.class, String.class, new PluginConfigConstraints.PluginVersion.Factory())
|
||
+ .addConstraint(PluginConfigConstraints.PluginNameSpace.class, String.class, new PluginConfigConstraints.PluginNameSpace.Factory())
|
||
+ .addNodeResolver(new FlattenedResolver.Factory())
|
||
+ .build()
|
||
+ );
|
||
+
|
||
+ });
|
||
+ })
|
||
+ .build();
|
||
+ CommentedConfigurationNode node = loader.load();
|
||
+ PaperPluginMeta pluginConfiguration = node.require(PaperPluginMeta.class);
|
||
+
|
||
+ if (!node.node("author").virtual()) {
|
||
+ pluginConfiguration.authors = ImmutableList.<String>builder()
|
||
+ .addAll(pluginConfiguration.authors)
|
||
+ .add(node.node("author").getString())
|
||
+ .build();
|
||
+ }
|
||
+
|
||
+ pluginConfiguration.displayName = pluginConfiguration.name.replace('_', ' ');
|
||
+
|
||
+ return pluginConfiguration;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getName() {
|
||
+ return this.name;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getMainClass() {
|
||
+ return this.main;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getVersion() {
|
||
+ return this.version;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getDisplayName() {
|
||
+ return this.displayName;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable String getLoggerPrefix() {
|
||
+ return this.prefix;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getPluginDependencies() {
|
||
+ return this.dependencies.stream().filter((dependency) -> dependency.required() && !dependency.bootstrap()).map(DependencyConfiguration::name).toList();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getPluginSoftDependencies() {
|
||
+ return this.dependencies.stream().filter((dependency) -> !dependency.required() && !dependency.bootstrap()).map(DependencyConfiguration::name).toList();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getLoadBeforePlugins() {
|
||
+ return this.loadBefore;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull PluginLoadOrder getLoadOrder() {
|
||
+ return this.load;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getDescription() {
|
||
+ return this.description;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getAuthors() {
|
||
+ return this.authors;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getContributors() {
|
||
+ return this.contributors;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String getWebsite() {
|
||
+ return this.website;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<Permission> getPermissions() {
|
||
+ return this.permissionConfiguration.permissions();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull PermissionDefault getPermissionDefault() {
|
||
+ return this.permissionConfiguration.defaultPerm();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getAPIVersion() {
|
||
+ return this.apiVersion;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getProvidedPlugins() {
|
||
+ return this.provides;
|
||
+ }
|
||
+
|
||
+ public String getBootstrapper() {
|
||
+ return this.bootstrapper;
|
||
+ }
|
||
+
|
||
+ public String getLoader() {
|
||
+ return this.loader;
|
||
+ }
|
||
+
|
||
+ public boolean hasOpenClassloader() {
|
||
+ return this.hasOpenClassloader;
|
||
+ }
|
||
+
|
||
+ public List<DependencyConfiguration> getDependencies() {
|
||
+ return dependencies;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..f0cdb1bab30faaa438aa3e6de6125ade3fae98c2
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableCollectionSerializer.java
|
||
@@ -0,0 +1,90 @@
|
||
+package io.papermc.paper.plugin.provider.configuration.serializer;
|
||
+
|
||
+import com.google.common.collect.ImmutableCollection;
|
||
+import org.checkerframework.checker.nullness.qual.Nullable;
|
||
+import org.spongepowered.configurate.ConfigurationNode;
|
||
+import org.spongepowered.configurate.ConfigurationOptions;
|
||
+import org.spongepowered.configurate.serialize.SerializationException;
|
||
+import org.spongepowered.configurate.serialize.TypeSerializer;
|
||
+import org.spongepowered.configurate.util.CheckedConsumer;
|
||
+
|
||
+import java.lang.reflect.Type;
|
||
+import java.util.Collection;
|
||
+import java.util.Collections;
|
||
+import java.util.List;
|
||
+
|
||
+@SuppressWarnings("unchecked")
|
||
+public abstract class ImmutableCollectionSerializer<B extends ImmutableCollection.Builder<?>, T extends Collection<?>> implements TypeSerializer<T> {
|
||
+
|
||
+ protected ImmutableCollectionSerializer() {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public final T deserialize(final Type type, final ConfigurationNode node) throws SerializationException {
|
||
+ final Type entryType = this.elementType(type);
|
||
+ final @Nullable TypeSerializer<?> entrySerial = node.options().serializers().get(entryType);
|
||
+ if (entrySerial == null) {
|
||
+ throw new SerializationException(node, entryType, "No applicable type serializer for type");
|
||
+ }
|
||
+
|
||
+ if (node.isList()) {
|
||
+ final List<? extends ConfigurationNode> values = node.childrenList();
|
||
+ final B builder = this.createNew(values.size());
|
||
+ for (ConfigurationNode value : values) {
|
||
+ try {
|
||
+ this.deserializeSingle(builder, entrySerial.deserialize(entryType, value));
|
||
+ } catch (final SerializationException ex) {
|
||
+ ex.initPath(value::path);
|
||
+ throw ex;
|
||
+ }
|
||
+ }
|
||
+ return (T) builder.build();
|
||
+ } else {
|
||
+ final @Nullable Object unwrappedVal = node.raw();
|
||
+ if (unwrappedVal != null) {
|
||
+ final B builder = this.createNew(1);
|
||
+ this.deserializeSingle(builder, entrySerial.deserialize(entryType, node));
|
||
+ return (T) builder.build();
|
||
+ }
|
||
+ }
|
||
+ return this.emptyValue(type, null);
|
||
+ }
|
||
+
|
||
+ @SuppressWarnings({"unchecked", "rawtypes"})
|
||
+ @Override
|
||
+ public final void serialize(final Type type, final @Nullable T obj, final ConfigurationNode node) throws SerializationException {
|
||
+ final Type entryType = this.elementType(type);
|
||
+ final @Nullable TypeSerializer entrySerial = node.options().serializers().get(entryType);
|
||
+ if (entrySerial == null) {
|
||
+ throw new SerializationException(node, entryType, "No applicable type serializer for type");
|
||
+ }
|
||
+
|
||
+ node.raw(Collections.emptyList());
|
||
+ if (obj != null) {
|
||
+ this.forEachElement(obj, el -> {
|
||
+ final ConfigurationNode child = node.appendListNode();
|
||
+ try {
|
||
+ entrySerial.serialize(entryType, el, child);
|
||
+ } catch (final SerializationException ex) {
|
||
+ ex.initPath(child::path);
|
||
+ throw ex;
|
||
+ }
|
||
+ });
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @SuppressWarnings({"unchecked"})
|
||
+ @Override
|
||
+ public @Nullable T emptyValue(final Type specificType, final ConfigurationOptions options) {
|
||
+ return (T) this.createNew(0).build();
|
||
+ }
|
||
+
|
||
+ protected abstract Type elementType(Type containerType) throws SerializationException;
|
||
+
|
||
+ protected abstract B createNew(int size);
|
||
+
|
||
+ protected abstract void forEachElement(T collection, CheckedConsumer<Object, SerializationException> action) throws SerializationException;
|
||
+
|
||
+ protected abstract void deserializeSingle(B builder, @Nullable Object deserialized) throws SerializationException;
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableListSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableListSerializer.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..7757d7df70e39a6fe4d92d02b6f905a22f80dbf3
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/ImmutableListSerializer.java
|
||
@@ -0,0 +1,43 @@
|
||
+package io.papermc.paper.plugin.provider.configuration.serializer;
|
||
+
|
||
+import com.google.common.collect.ImmutableList;
|
||
+import org.checkerframework.checker.nullness.qual.Nullable;
|
||
+import org.spongepowered.configurate.serialize.SerializationException;
|
||
+import org.spongepowered.configurate.util.CheckedConsumer;
|
||
+
|
||
+import java.lang.reflect.ParameterizedType;
|
||
+import java.lang.reflect.Type;
|
||
+import java.util.List;
|
||
+
|
||
+public class ImmutableListSerializer extends ImmutableCollectionSerializer<ImmutableList.Builder<?>, List<?>> {
|
||
+
|
||
+ @Override
|
||
+ protected Type elementType(Type containerType) throws SerializationException {
|
||
+ if (!(containerType instanceof ParameterizedType)) {
|
||
+ throw new SerializationException(containerType, "Raw types are not supported for collections");
|
||
+ }
|
||
+ return ((ParameterizedType) containerType).getActualTypeArguments()[0];
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ protected ImmutableList.Builder<?> createNew(int size) {
|
||
+ return ImmutableList.builderWithExpectedSize(size);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ protected void forEachElement(List<?> collection, CheckedConsumer<Object, SerializationException> action) throws SerializationException {
|
||
+ for (Object obj : collection) {
|
||
+ action.accept(obj);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @SuppressWarnings({"unchecked", "rawtypes"})
|
||
+ @Override
|
||
+ protected void deserializeSingle(ImmutableList.Builder<?> builder, @Nullable Object deserialized) throws SerializationException {
|
||
+ if (deserialized == null) {
|
||
+ return;
|
||
+ }
|
||
+
|
||
+ ((ImmutableList.Builder) builder).add(deserialized);
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..d1088e4b7fa5f8e689f23b150b83645ce1ae5a0e
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/PermissionConfigurationSerializer.java
|
||
@@ -0,0 +1,56 @@
|
||
+package io.papermc.paper.plugin.provider.configuration.serializer;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.configuration.type.PermissionConfiguration;
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.permissions.PermissionDefault;
|
||
+import org.spongepowered.configurate.ConfigurationNode;
|
||
+import org.spongepowered.configurate.serialize.SerializationException;
|
||
+import org.spongepowered.configurate.serialize.TypeSerializer;
|
||
+
|
||
+import java.lang.reflect.Type;
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+
|
||
+public class PermissionConfigurationSerializer {
|
||
+
|
||
+ public static final Serializer SERIALIZER = new Serializer();
|
||
+
|
||
+ private static final class Serializer implements TypeSerializer<PermissionConfiguration> {
|
||
+ private Serializer() {
|
||
+ super();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PermissionConfiguration deserialize(Type type, ConfigurationNode node) throws SerializationException {
|
||
+ Map<?, ?> map = (Map<?, ?>) node.node("permissions").raw();
|
||
+
|
||
+ PermissionDefault permissionDefault;
|
||
+ ConfigurationNode permNode = node.node("defaultPerm");
|
||
+ if (permNode.virtual()) {
|
||
+ permissionDefault = PermissionDefault.OP;
|
||
+ } else {
|
||
+ permissionDefault = PermissionDefault.getByName(permNode.getString());
|
||
+ }
|
||
+
|
||
+ List<Permission> result = new ArrayList<>();
|
||
+ if (map != null) {
|
||
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||
+ try {
|
||
+ result.add(Permission.loadPermission(entry.getKey().toString(), (Map<?, ?>) entry.getValue(), permissionDefault, result));
|
||
+ } catch (Throwable ex) {
|
||
+ throw new SerializationException(null, "Error loading permission %s".formatted(entry.getKey()), ex);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return new PermissionConfiguration(permissionDefault, List.copyOf(result));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void serialize(Type type, @org.checkerframework.checker.nullness.qual.Nullable PermissionConfiguration obj, ConfigurationNode node) throws SerializationException {
|
||
+
|
||
+ }
|
||
+
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/constraints/PluginConfigConstraints.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/constraints/PluginConfigConstraints.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..a0109a388188b0808900405d334a40318ab16ec1
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/serializer/constraints/PluginConfigConstraints.java
|
||
@@ -0,0 +1,86 @@
|
||
+package io.papermc.paper.plugin.provider.configuration.serializer.constraints;
|
||
+
|
||
+import io.papermc.paper.plugin.util.NamespaceChecker;
|
||
+import org.spongepowered.configurate.objectmapping.meta.Constraint;
|
||
+import org.spongepowered.configurate.serialize.SerializationException;
|
||
+
|
||
+import java.lang.annotation.Documented;
|
||
+import java.lang.annotation.ElementType;
|
||
+import java.lang.annotation.Retention;
|
||
+import java.lang.annotation.RetentionPolicy;
|
||
+import java.lang.annotation.Target;
|
||
+import java.lang.reflect.Type;
|
||
+import java.util.Locale;
|
||
+import java.util.Set;
|
||
+import java.util.regex.Pattern;
|
||
+
|
||
+public final class PluginConfigConstraints {
|
||
+
|
||
+ public static final Set<String> RESERVED_KEYS = Set.of("bukkit", "minecraft", "mojang", "spigot", "paper");
|
||
+ public static final Set<String> VALID_PAPER_VERSIONS = Set.of("1.19");
|
||
+
|
||
+ @Documented
|
||
+ @Retention(RetentionPolicy.RUNTIME)
|
||
+ @Target(ElementType.FIELD)
|
||
+ public @interface PluginName {
|
||
+
|
||
+ final class Factory implements Constraint.Factory<PluginName, String> {
|
||
+
|
||
+ private static final Pattern VALID_NAME = Pattern.compile("^[A-Za-z\\d _.-]+$");
|
||
+
|
||
+ @Override
|
||
+ public Constraint<String> make(PluginName data, Type type) {
|
||
+ return value -> {
|
||
+ if (value != null) {
|
||
+ if (RESERVED_KEYS.contains(value.toLowerCase(Locale.ROOT))) {
|
||
+ throw new SerializationException("Restricted name, cannot use '%s' as a plugin name.".formatted(data));
|
||
+ } else if (value.indexOf(' ') != -1) {
|
||
+ // For legacy reasons, the space condition has a separate exception message.
|
||
+ throw new SerializationException("Restricted name, cannot use 0x20 (space character) in a plugin name.");
|
||
+ }
|
||
+
|
||
+ if (!VALID_NAME.matcher(value).matches()) {
|
||
+ throw new SerializationException("name '" + value + "' contains invalid characters.");
|
||
+ }
|
||
+ }
|
||
+ };
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Documented
|
||
+ @Retention(RetentionPolicy.RUNTIME)
|
||
+ @Target(ElementType.FIELD)
|
||
+ public @interface PluginNameSpace {
|
||
+
|
||
+ final class Factory implements Constraint.Factory<PluginNameSpace, String> {
|
||
+
|
||
+ @Override
|
||
+ public Constraint<String> make(PluginNameSpace data, Type type) {
|
||
+ return value -> {
|
||
+ if (value != null && !NamespaceChecker.isValidNameSpace(value)) {
|
||
+ throw new SerializationException("provided class '%s' is in an invalid namespace.".formatted(value));
|
||
+ }
|
||
+ };
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Documented
|
||
+ @Retention(RetentionPolicy.RUNTIME)
|
||
+ @Target(ElementType.FIELD)
|
||
+ public @interface PluginVersion {
|
||
+
|
||
+ final class Factory implements Constraint.Factory<PluginVersion, String> {
|
||
+
|
||
+ @Override
|
||
+ public Constraint<String> make(PluginVersion data, Type type) {
|
||
+ return value -> {
|
||
+ if (value != null && !VALID_PAPER_VERSIONS.contains(value)) {
|
||
+ throw new SerializationException("Provided plugin's version (%s) is not supported on this version.".formatted(value));
|
||
+ }
|
||
+ };
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/DependencyConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/DependencyConfiguration.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..071bff3f988a4391be424bdf7e98a6c35e6cac67
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/DependencyConfiguration.java
|
||
@@ -0,0 +1,12 @@
|
||
+package io.papermc.paper.plugin.provider.configuration.type;
|
||
+
|
||
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||
+import org.spongepowered.configurate.objectmapping.meta.Required;
|
||
+
|
||
+@ConfigSerializable
|
||
+public record DependencyConfiguration(
|
||
+ @Required String name,
|
||
+ boolean required,
|
||
+ boolean bootstrap
|
||
+) {
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..a180612a1ec395202dbae1ca5b97ec01382097e4
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/configuration/type/PermissionConfiguration.java
|
||
@@ -0,0 +1,14 @@
|
||
+package io.papermc.paper.plugin.provider.configuration.type;
|
||
+
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.permissions.PermissionDefault;
|
||
+import org.spongepowered.configurate.objectmapping.ConfigSerializable;
|
||
+
|
||
+import java.util.List;
|
||
+
|
||
+// Record components used for deserialization!!!!
|
||
+@ConfigSerializable
|
||
+public record PermissionConfiguration(
|
||
+ PermissionDefault defaultPerm,
|
||
+ List<Permission> permissions) {
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..1822e076601db51c8a7954036853bee1fb8e3704
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/source/DirectoryProviderSource.java
|
||
@@ -0,0 +1,40 @@
|
||
+package io.papermc.paper.plugin.provider.source;
|
||
+
|
||
+import com.mojang.logging.LogUtils;
|
||
+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+import java.nio.file.Files;
|
||
+import java.nio.file.Path;
|
||
+import java.util.logging.Level;
|
||
+
|
||
+/**
|
||
+ * Loads all plugin providers in the given directory.
|
||
+ */
|
||
+public class DirectoryProviderSource extends FileProviderSource {
|
||
+
|
||
+ public static final DirectoryProviderSource INSTANCE = new DirectoryProviderSource();
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+
|
||
+ public DirectoryProviderSource() {
|
||
+ super("Directory '%s'"::formatted);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void registerProviders(EntrypointHandler entrypointHandler, Path context) throws Exception {
|
||
+ // Sym link happy, create file if missing.
|
||
+ if (!Files.isDirectory(context)) {
|
||
+ Files.createDirectories(context);
|
||
+ }
|
||
+
|
||
+ Files.walk(context, 1).filter(Files::isRegularFile).forEach((path) -> {
|
||
+ try {
|
||
+ super.registerProviders(entrypointHandler, path);
|
||
+ } catch (IllegalArgumentException ignored) {
|
||
+ // Ignore initial argument exceptions
|
||
+ } catch (Exception e) {
|
||
+ LOGGER.error("Error loading plugin: " + e.getMessage(), e);
|
||
+ }
|
||
+ });
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..e6a99f422038fad519215abf239135b11edc2bce
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/source/FileProviderSource.java
|
||
@@ -0,0 +1,156 @@
|
||
+package io.papermc.paper.plugin.provider.source;
|
||
+
|
||
+import io.papermc.paper.plugin.PluginInitializerManager;
|
||
+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
|
||
+import io.papermc.paper.plugin.provider.type.PluginFileType;
|
||
+import org.bukkit.plugin.InvalidPluginException;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.io.File;
|
||
+import java.io.IOException;
|
||
+import java.nio.file.FileVisitResult;
|
||
+import java.nio.file.FileVisitor;
|
||
+import java.nio.file.Files;
|
||
+import java.nio.file.Path;
|
||
+import java.nio.file.StandardCopyOption;
|
||
+import java.nio.file.attribute.BasicFileAttributes;
|
||
+import java.util.Set;
|
||
+import java.util.function.Function;
|
||
+import java.util.jar.JarFile;
|
||
+
|
||
+/**
|
||
+ * Loads a plugin provider at the given plugin jar file path.
|
||
+ */
|
||
+public class FileProviderSource implements ProviderSource<Path> {
|
||
+
|
||
+ private final Function<Path, String> contextChecker;
|
||
+
|
||
+ public FileProviderSource(Function<Path, String> contextChecker) {
|
||
+ this.contextChecker = contextChecker;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void registerProviders(EntrypointHandler entrypointHandler, Path context) throws Exception {
|
||
+ String source = this.contextChecker.apply(context);
|
||
+
|
||
+ if (Files.notExists(context)) {
|
||
+ throw new IllegalArgumentException(source + " does not exist, cannot load a plugin from it!");
|
||
+ }
|
||
+
|
||
+ if (!Files.isRegularFile(context)) {
|
||
+ throw new IllegalArgumentException(source + " is not a file, cannot load a plugin from it!");
|
||
+ }
|
||
+
|
||
+ if (!context.getFileName().toString().endsWith(".jar")) {
|
||
+ throw new IllegalArgumentException(source + " is not a jar file, cannot load a plugin from it!");
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ context = this.checkUpdate(context);
|
||
+
|
||
+ JarFile file = new JarFile(context.toFile());
|
||
+ PluginFileType<?, ?> type = PluginFileType.guessType(file);
|
||
+ if (type == null) {
|
||
+ throw new IllegalArgumentException(source + " is not a valid plugin file, cannot load a plugin from it!");
|
||
+ }
|
||
+
|
||
+ type.register(entrypointHandler, file, context);
|
||
+ } catch (Exception exception) {
|
||
+ throw new RuntimeException(source + " failed to load!", exception);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Replaces a plugin with a plugin of the same plugin name in the update folder.
|
||
+ *
|
||
+ * @param file
|
||
+ */
|
||
+ private Path checkUpdate(Path file) throws Exception {
|
||
+ PluginInitializerManager pluginSystem = PluginInitializerManager.instance();
|
||
+ Path updateDirectory = pluginSystem.pluginUpdatePath();
|
||
+ if (updateDirectory == null || !Files.isDirectory(updateDirectory)) {
|
||
+ return file;
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ String pluginName = this.getPluginName(file);
|
||
+ UpdateFileVisitor visitor = new UpdateFileVisitor(pluginName);
|
||
+ Files.walkFileTree(updateDirectory, Set.of(), 1, visitor);
|
||
+ if (visitor.getValidPlugin() != null) {
|
||
+ Path updateLocation = visitor.getValidPlugin();
|
||
+
|
||
+ try {
|
||
+ Files.copy(updateLocation, file, StandardCopyOption.REPLACE_EXISTING);
|
||
+ } catch (IOException exception) {
|
||
+ throw new RuntimeException("Could not copy '" + updateLocation + "' to '" + file + "' in update plugin process", exception);
|
||
+ }
|
||
+
|
||
+ // Idk what this is about, TODO
|
||
+ File newName = new File(file.toFile().getParentFile(), updateLocation.toFile().getName());
|
||
+ file.toFile().renameTo(newName);
|
||
+ updateLocation.toFile().delete();
|
||
+ return newName.toPath();
|
||
+ }
|
||
+ } catch (Exception e) {
|
||
+ throw new InvalidPluginException(e);
|
||
+ }
|
||
+ return file;
|
||
+ }
|
||
+
|
||
+ private String getPluginName(Path path) throws Exception {
|
||
+ JarFile file = new JarFile(path.toFile());
|
||
+ PluginFileType<?, ?> type = PluginFileType.guessType(file);
|
||
+ if (type == null) {
|
||
+ throw new IllegalArgumentException(path + " is not a valid plugin file, cannot load a plugin from it!");
|
||
+ }
|
||
+
|
||
+ return type.getConfig(file).getName();
|
||
+ }
|
||
+
|
||
+ private class UpdateFileVisitor implements FileVisitor<Path> {
|
||
+
|
||
+ private final String targetName;
|
||
+ @Nullable
|
||
+ private Path validPlugin;
|
||
+
|
||
+ private UpdateFileVisitor(String targetName) {
|
||
+ this.targetName = targetName;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
|
||
+ return FileVisitResult.CONTINUE;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||
+ try {
|
||
+ String updatePluginName = FileProviderSource.this.getPluginName(file);
|
||
+ if (this.targetName.equals(updatePluginName)) {
|
||
+ this.validPlugin = file;
|
||
+ return FileVisitResult.TERMINATE;
|
||
+ }
|
||
+ } catch (Exception e) {
|
||
+ // We failed to load this data for some reason, so, we'll skip over this
|
||
+ }
|
||
+
|
||
+
|
||
+ return FileVisitResult.CONTINUE;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
|
||
+ return FileVisitResult.CONTINUE;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||
+ return FileVisitResult.CONTINUE;
|
||
+ }
|
||
+
|
||
+ @Nullable
|
||
+ public Path getValidPlugin() {
|
||
+ return validPlugin;
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/PluginFlagProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/PluginFlagProviderSource.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..e62b476ddbce2bdc66061c116aa0228622f6fd16
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/source/PluginFlagProviderSource.java
|
||
@@ -0,0 +1,29 @@
|
||
+package io.papermc.paper.plugin.provider.source;
|
||
+
|
||
+import com.mojang.logging.LogUtils;
|
||
+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+import java.io.File;
|
||
+import java.util.List;
|
||
+
|
||
+/**
|
||
+ * Registers providers at the provided files in the add-plugin argument.
|
||
+ */
|
||
+public class PluginFlagProviderSource implements ProviderSource<List<File>> {
|
||
+
|
||
+ public static final PluginFlagProviderSource INSTANCE = new PluginFlagProviderSource();
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+ private final FileProviderSource providerSource = new FileProviderSource("File '%s' specified through 'add-plugin' argument"::formatted);
|
||
+
|
||
+ @Override
|
||
+ public void registerProviders(EntrypointHandler entrypointHandler, List<File> context) {
|
||
+ for (File file : context) {
|
||
+ try {
|
||
+ this.providerSource.registerProviders(entrypointHandler, file.toPath());
|
||
+ } catch (Exception e) {
|
||
+ LOGGER.error("Error loading plugin: " + e.getMessage(), e);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/source/ProviderSource.java b/src/main/java/io/papermc/paper/plugin/provider/source/ProviderSource.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..6d247819ee842eb054a74711a0e5805ac8f0498e
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/source/ProviderSource.java
|
||
@@ -0,0 +1,14 @@
|
||
+package io.papermc.paper.plugin.provider.source;
|
||
+
|
||
+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
|
||
+
|
||
+/**
|
||
+ * A provider source is responsible for giving PluginTypes an EntrypointHandler for
|
||
+ * registering providers at.
|
||
+ *
|
||
+ * @param <C> context
|
||
+ */
|
||
+public interface ProviderSource<C> {
|
||
+
|
||
+ void registerProviders(EntrypointHandler entrypointHandler, C context) throws Throwable;
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java b/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..22c25dc6fdfd336f5074fa52c3a4e8128d433ccc
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/PluginFileType.java
|
||
@@ -0,0 +1,77 @@
|
||
+package io.papermc.paper.plugin.provider.type;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import io.papermc.paper.plugin.entrypoint.Entrypoint;
|
||
+import io.papermc.paper.plugin.entrypoint.EntrypointHandler;
|
||
+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||
+import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent;
|
||
+import io.papermc.paper.plugin.provider.type.spigot.SpigotPluginProvider;
|
||
+import org.bukkit.plugin.PluginDescriptionFile;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.nio.file.Path;
|
||
+import java.util.List;
|
||
+import java.util.jar.JarEntry;
|
||
+import java.util.jar.JarFile;
|
||
+
|
||
+/**
|
||
+ * This is where spigot/paper plugins are registered.
|
||
+ * This will get the jar and find a certain config file, create an object
|
||
+ * then registering it into a {@link EntrypointHandler} at a certain {@link Entrypoint}.
|
||
+ */
|
||
+public abstract class PluginFileType<T, C extends PluginMeta> {
|
||
+
|
||
+ public static final PluginFileType<PaperPluginParent, PaperPluginMeta> PAPER = new PluginFileType<>("paper-plugin.yml", PaperPluginParent.FACTORY) {
|
||
+ @Override
|
||
+ protected void register(EntrypointHandler entrypointHandler, PaperPluginParent parent) {
|
||
+ PaperPluginParent.PaperBootstrapProvider bootstrapPluginProvider = null;
|
||
+ if (parent.shouldCreateBootstrap()) {
|
||
+ bootstrapPluginProvider = parent.createBootstrapProvider();
|
||
+ entrypointHandler.register(Entrypoint.BOOTSTRAPPER, bootstrapPluginProvider);
|
||
+ }
|
||
+
|
||
+ entrypointHandler.register(Entrypoint.PLUGIN, parent.createPluginProvider(bootstrapPluginProvider));
|
||
+ }
|
||
+ };
|
||
+ public static final PluginFileType<SpigotPluginProvider, PluginDescriptionFile> SPIGOT = new PluginFileType<>("plugin.yml", SpigotPluginProvider.FACTORY) {
|
||
+ @Override
|
||
+ protected void register(EntrypointHandler entrypointHandler, SpigotPluginProvider provider) {
|
||
+ entrypointHandler.register(Entrypoint.PLUGIN, provider);
|
||
+ }
|
||
+ };
|
||
+
|
||
+ private static final List<PluginFileType<?, ?>> VALUES = List.of(PAPER, SPIGOT);
|
||
+
|
||
+ private final String config;
|
||
+ private final PluginTypeFactory<T, C> factory;
|
||
+
|
||
+ PluginFileType(String config, PluginTypeFactory<T, C> factory) {
|
||
+ this.config = config;
|
||
+ this.factory = factory;
|
||
+ }
|
||
+
|
||
+ @Nullable
|
||
+ public static PluginFileType<?, ?> guessType(JarFile file) {
|
||
+ for (PluginFileType<?, ?> type : VALUES) {
|
||
+ JarEntry entry = file.getJarEntry(type.config);
|
||
+ if (entry != null) {
|
||
+ return type;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return null;
|
||
+ }
|
||
+
|
||
+ public T register(EntrypointHandler entrypointHandler, JarFile file, Path context) throws Exception {
|
||
+ C config = this.getConfig(file);
|
||
+ T provider = this.factory.build(file, config, context);
|
||
+ this.register(entrypointHandler, provider);
|
||
+ return provider;
|
||
+ }
|
||
+
|
||
+ public C getConfig(JarFile file) throws Exception {
|
||
+ return this.factory.create(file, file.getJarEntry(this.config));
|
||
+ }
|
||
+
|
||
+ protected abstract void register(EntrypointHandler entrypointHandler, T provider);
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..32f230d66f6953520b59ccbf3079c5a6242ca92c
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/PluginTypeFactory.java
|
||
@@ -0,0 +1,21 @@
|
||
+package io.papermc.paper.plugin.provider.type;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+
|
||
+import java.nio.file.Path;
|
||
+import java.util.jar.JarEntry;
|
||
+import java.util.jar.JarFile;
|
||
+
|
||
+/**
|
||
+ * A plugin type factory is responsible for building an object
|
||
+ * and config for a certain plugin type.
|
||
+ *
|
||
+ * @param <T> plugin provider type (may not be a plugin provider)
|
||
+ * @param <C> config type
|
||
+ */
|
||
+public interface PluginTypeFactory<T, C extends PluginMeta> {
|
||
+
|
||
+ T build(JarFile file, C configuration, Path source) throws Exception;
|
||
+
|
||
+ C create(JarFile file, JarEntry config) throws Exception;
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..46ee1b7f4b4c509932c68a4152e1d8445811d3b8
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginParent.java
|
||
@@ -0,0 +1,226 @@
|
||
+package io.papermc.paper.plugin.provider.type.paper;
|
||
+
|
||
+import com.destroystokyo.paper.util.SneakyThrow;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatus;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatusHolder;
|
||
+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||
+import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
|
||
+import io.papermc.paper.plugin.provider.util.ProviderUtil;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.nio.file.Path;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+public class PaperPluginParent {
|
||
+
|
||
+ public static final PluginTypeFactory<PaperPluginParent, PaperPluginMeta> FACTORY = new PaperPluginProviderFactory();
|
||
+ private final Path path;
|
||
+ private final JarFile jarFile;
|
||
+ private final PaperPluginMeta description;
|
||
+ private final PaperPluginClassLoader classLoader;
|
||
+ private final PluginProviderContext context;
|
||
+ private final Logger logger;
|
||
+
|
||
+ public PaperPluginParent(Path path, JarFile jarFile, PaperPluginMeta description, PaperPluginClassLoader classLoader, PluginProviderContext context) {
|
||
+ this.path = path;
|
||
+ this.jarFile = jarFile;
|
||
+ this.description = description;
|
||
+ this.classLoader = classLoader;
|
||
+ this.context = context;
|
||
+ this.logger = context.getLogger();
|
||
+ }
|
||
+
|
||
+ public boolean shouldCreateBootstrap() {
|
||
+ return this.description.getBootstrapper() != null;
|
||
+ }
|
||
+
|
||
+ public PaperBootstrapProvider createBootstrapProvider() {
|
||
+ return new PaperBootstrapProvider();
|
||
+ }
|
||
+
|
||
+ public PaperServerPluginProvider createPluginProvider(PaperBootstrapProvider provider) {
|
||
+ return new PaperServerPluginProvider(provider);
|
||
+ }
|
||
+
|
||
+ public class PaperBootstrapProvider implements PluginProvider<PluginBootstrap>, ProviderStatusHolder, DependencyContextHolder {
|
||
+
|
||
+ private ProviderStatus status;
|
||
+ private PluginBootstrap lastProvided;
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Path getSource() {
|
||
+ return PaperPluginParent.this.path;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public JarFile file() {
|
||
+ return PaperPluginParent.this.jarFile;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginBootstrap createInstance() {
|
||
+ PluginBootstrap bootstrap = ProviderUtil.loadClass(PaperPluginParent.this.description.getBootstrapper(),
|
||
+ PluginBootstrap.class, PaperPluginParent.this.classLoader, () -> this.status = ProviderStatus.ERRORED);
|
||
+ this.status = ProviderStatus.INITIALIZED;
|
||
+ this.lastProvided = bootstrap;
|
||
+ return bootstrap;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PaperPluginMeta getMeta() {
|
||
+ return PaperPluginParent.this.description;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Logger getLogger() {
|
||
+ return PaperPluginParent.this.logger;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ProviderStatus getLastProvidedStatus() {
|
||
+ return this.status;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void setStatus(ProviderStatus status) {
|
||
+ this.status = status;
|
||
+ }
|
||
+
|
||
+ public PluginBootstrap getLastProvided() {
|
||
+ return this.lastProvided;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void setContext(DependencyContext context) {
|
||
+ PaperPluginParent.this.classLoader.refreshClassloaderDependencyTree(context);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "PaperBootstrapProvider{" +
|
||
+ "parent=" + PaperPluginParent.this +
|
||
+ "status=" + status +
|
||
+ ", lastProvided=" + lastProvided +
|
||
+ '}';
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public class PaperServerPluginProvider implements PluginProvider<JavaPlugin>, ProviderStatusHolder, DependencyContextHolder {
|
||
+
|
||
+ private final PaperBootstrapProvider bootstrapProvider;
|
||
+
|
||
+ private ProviderStatus status;
|
||
+
|
||
+ PaperServerPluginProvider(PaperBootstrapProvider bootstrapProvider) {
|
||
+ this.bootstrapProvider = bootstrapProvider;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Path getSource() {
|
||
+ return PaperPluginParent.this.path;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public JarFile file() {
|
||
+ return PaperPluginParent.this.jarFile;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public JavaPlugin createInstance() {
|
||
+ PluginBootstrap bootstrap = null;
|
||
+ if (this.bootstrapProvider != null && this.bootstrapProvider.getLastProvided() != null) {
|
||
+ bootstrap = this.bootstrapProvider.getLastProvided();
|
||
+ }
|
||
+
|
||
+ try {
|
||
+ JavaPlugin plugin;
|
||
+ if (bootstrap == null) {
|
||
+ plugin = ProviderUtil.loadClass(PaperPluginParent.this.description.getMainClass(), JavaPlugin.class, PaperPluginParent.this.classLoader);
|
||
+ } else {
|
||
+ plugin = bootstrap.createPlugin(PaperPluginParent.this.context);
|
||
+ }
|
||
+
|
||
+ // Don't allow plugins to load plugins other than the one defined in main. This restriction might not be necessary.
|
||
+ if (!plugin.getClass().isAssignableFrom(Class.forName(PaperPluginParent.this.description.getMainClass(), true, plugin.getClass().getClassLoader()))) {
|
||
+ throw new IllegalArgumentException("Plugin provided must be the same type as main defined in plugin configuration!");
|
||
+ }
|
||
+
|
||
+ this.status = ProviderStatus.INITIALIZED;
|
||
+ return plugin;
|
||
+ } catch (Throwable throwable) {
|
||
+ this.status = ProviderStatus.ERRORED;
|
||
+ SneakyThrow.sneaky(throwable);
|
||
+ }
|
||
+
|
||
+ throw new AssertionError(); // Impossible
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PaperPluginMeta getMeta() {
|
||
+ return PaperPluginParent.this.description;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Logger getLogger() {
|
||
+ return PaperPluginParent.this.logger;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ProviderStatus getLastProvidedStatus() {
|
||
+ return this.status;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void setStatus(ProviderStatus status) {
|
||
+ this.status = status;
|
||
+ }
|
||
+
|
||
+ public boolean shouldSkipCreation() {
|
||
+ if (this.bootstrapProvider == null) {
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ return this.bootstrapProvider.getLastProvidedStatus() == ProviderStatus.ERRORED;
|
||
+ }
|
||
+
|
||
+ /*
|
||
+ The plugin has to reuse the classloader in order to share the bootstrapper.
|
||
+ However, a plugin may have totally separate dependencies during bootstrapping.
|
||
+ This is a bit yuck, but in general we have to treat bootstrapping and normal game as connected.
|
||
+ */
|
||
+ @Override
|
||
+ public void setContext(DependencyContext context) {
|
||
+ PaperPluginParent.this.classLoader.refreshClassloaderDependencyTree(context);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "PaperServerPluginProvider{" +
|
||
+ "parent=" + PaperPluginParent.this +
|
||
+ "bootstrapProvider=" + bootstrapProvider +
|
||
+ ", status=" + status +
|
||
+ '}';
|
||
+ }
|
||
+ }
|
||
+
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "PaperPluginParent{" +
|
||
+ "path=" + path +
|
||
+ ", jarFile=" + jarFile +
|
||
+ ", description=" + description +
|
||
+ ", classLoader=" + classLoader +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..db343a2f482ac375078610f0875692861f412ee2
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/paper/PaperPluginProviderFactory.java
|
||
@@ -0,0 +1,54 @@
|
||
+package io.papermc.paper.plugin.provider.type.paper;
|
||
+
|
||
+import com.destroystokyo.paper.utils.PaperPluginLogger;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.PaperSimplePluginClassLoader;
|
||
+import io.papermc.paper.plugin.loader.PaperClasspathBuilder;
|
||
+import io.papermc.paper.plugin.loader.PluginLoader;
|
||
+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||
+import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
|
||
+import io.papermc.paper.plugin.provider.util.ProviderUtil;
|
||
+
|
||
+import java.io.BufferedReader;
|
||
+import java.io.IOException;
|
||
+import java.io.InputStreamReader;
|
||
+import java.nio.file.Path;
|
||
+import java.util.jar.JarEntry;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+class PaperPluginProviderFactory implements PluginTypeFactory<PaperPluginParent, PaperPluginMeta> {
|
||
+
|
||
+ @Override
|
||
+ public PaperPluginParent build(JarFile file, PaperPluginMeta configuration, Path source) throws Exception {
|
||
+ Logger logger = PaperPluginLogger.getLogger(configuration);
|
||
+ PluginProviderContext context = PluginProviderContextImpl.of(configuration, logger);
|
||
+
|
||
+ PaperClasspathBuilder builder = new PaperClasspathBuilder(context);
|
||
+
|
||
+ if (configuration.getLoader() != null) {
|
||
+ try (
|
||
+ PaperSimplePluginClassLoader simplePluginClassLoader = new PaperSimplePluginClassLoader(source, file, configuration, this.getClass().getClassLoader())
|
||
+ ) {
|
||
+ PluginLoader loader = ProviderUtil.loadClass(configuration.getLoader(), PluginLoader.class, simplePluginClassLoader);
|
||
+ loader.classloader(builder);
|
||
+ } catch (IOException e) {
|
||
+ throw new RuntimeException(e);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ PaperPluginClassLoader classLoader = builder.buildClassLoader(logger, source, file, configuration);
|
||
+ return new PaperPluginParent(source, file, configuration, classLoader, context);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PaperPluginMeta create(JarFile file, JarEntry config) throws Exception {
|
||
+ PaperPluginMeta configuration;
|
||
+ try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(file.getInputStream(config)))) {
|
||
+ configuration = PaperPluginMeta.create(bufferedReader);
|
||
+ }
|
||
+ return configuration;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..309c3dc492cbf469768d6a712485a0e42e73f947
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProvider.java
|
||
@@ -0,0 +1,172 @@
|
||
+package io.papermc.paper.plugin.provider.type.spigot;
|
||
+
|
||
+import com.destroystokyo.paper.util.SneakyThrow;
|
||
+import com.destroystokyo.paper.utils.PaperPluginLogger;
|
||
+import io.papermc.paper.plugin.manager.PaperPluginManagerImpl;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatus;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatusHolder;
|
||
+import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.Server;
|
||
+import org.bukkit.plugin.InvalidPluginException;
|
||
+import org.bukkit.plugin.PluginDescriptionFile;
|
||
+import org.bukkit.plugin.UnknownDependencyException;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.bukkit.plugin.java.LibraryLoader;
|
||
+import org.bukkit.plugin.java.PluginClassLoader;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.io.File;
|
||
+import java.nio.file.Path;
|
||
+import java.util.HashSet;
|
||
+import java.util.Set;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.logging.Level;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+public class SpigotPluginProvider implements PluginProvider<JavaPlugin>, ProviderStatusHolder, DependencyContextHolder {
|
||
+
|
||
+ public static final PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> FACTORY = new SpigotPluginProviderFactory();
|
||
+ private static final LibraryLoader LIBRARY_LOADER = new LibraryLoader(Logger.getLogger("SpigotLibraryLoader"));
|
||
+ private final Path path;
|
||
+ private final PluginDescriptionFile description;
|
||
+ private final JarFile jarFile;
|
||
+ private final Logger logger;
|
||
+ private ProviderStatus status;
|
||
+ private DependencyContext dependencyContext;
|
||
+
|
||
+ SpigotPluginProvider(Path path, JarFile file, PluginDescriptionFile description) {
|
||
+ this.path = path;
|
||
+ this.jarFile = file;
|
||
+ this.description = description;
|
||
+ this.logger = PaperPluginLogger.getLogger(description);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Path getSource() {
|
||
+ return this.path;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public JarFile file() {
|
||
+ return this.jarFile;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public JavaPlugin createInstance() {
|
||
+ Server server = Bukkit.getServer();
|
||
+ try {
|
||
+
|
||
+ final File parentFile = server.getPluginsFolder(); // Paper
|
||
+ final File dataFolder = new File(parentFile, this.description.getName());
|
||
+ @SuppressWarnings("deprecation") final File oldDataFolder = new File(parentFile, this.description.getRawName());
|
||
+
|
||
+ // Found old data folder
|
||
+ if (dataFolder.equals(oldDataFolder)) {
|
||
+ // They are equal -- nothing needs to be done!
|
||
+ } else if (dataFolder.isDirectory() && oldDataFolder.isDirectory()) {
|
||
+ server.getLogger().warning(String.format(
|
||
+ "While loading %s (%s) found old-data folder: `%s' next to the new one `%s'",
|
||
+ this.description.getFullName(),
|
||
+ this.path,
|
||
+ oldDataFolder,
|
||
+ dataFolder
|
||
+ ));
|
||
+ } else if (oldDataFolder.isDirectory() && !dataFolder.exists()) {
|
||
+ if (!oldDataFolder.renameTo(dataFolder)) {
|
||
+ throw new InvalidPluginException("Unable to rename old data folder: `" + oldDataFolder + "' to: `" + dataFolder + "'");
|
||
+ }
|
||
+ server.getLogger().log(Level.INFO, String.format(
|
||
+ "While loading %s (%s) renamed data folder: `%s' to `%s'",
|
||
+ this.description.getFullName(),
|
||
+ this.path,
|
||
+ oldDataFolder,
|
||
+ dataFolder
|
||
+ ));
|
||
+ }
|
||
+
|
||
+ if (dataFolder.exists() && !dataFolder.isDirectory()) {
|
||
+ throw new InvalidPluginException(String.format(
|
||
+ "Projected datafolder: `%s' for %s (%s) exists and is not a directory",
|
||
+ dataFolder,
|
||
+ this.description.getFullName(),
|
||
+ this.path
|
||
+ ));
|
||
+ }
|
||
+
|
||
+ Set<String> missingHardDependencies = new HashSet<>(this.description.getDepend().size()); // Paper - list all missing hard depends
|
||
+ for (final String pluginName : this.description.getDepend()) {
|
||
+ if (!this.dependencyContext.hasDependency(pluginName)) {
|
||
+ missingHardDependencies.add(pluginName); // Paper - list all missing hard depends
|
||
+ }
|
||
+ }
|
||
+ // Paper start - list all missing hard depends
|
||
+ if (!missingHardDependencies.isEmpty()) {
|
||
+ throw new UnknownDependencyException(missingHardDependencies, this.description.getFullName());
|
||
+ }
|
||
+ // Paper end
|
||
+
|
||
+ server.getUnsafe().checkSupported(this.description);
|
||
+
|
||
+ final PluginClassLoader loader;
|
||
+ try {
|
||
+ loader = new PluginClassLoader(this.getClass().getClassLoader(), this.description, dataFolder, this.path.toFile(), LIBRARY_LOADER.createLoader(this.description), this.dependencyContext); // Paper
|
||
+ } catch (InvalidPluginException ex) {
|
||
+ throw ex;
|
||
+ } catch (Throwable ex) {
|
||
+ throw new InvalidPluginException(ex);
|
||
+ }
|
||
+
|
||
+ // Override dependency context.
|
||
+ // We must provide a temporary context in order to properly handle dependencies on the plugin classloader constructor.
|
||
+ loader.dependencyContext = PaperPluginManagerImpl.getInstance();
|
||
+
|
||
+ this.status = ProviderStatus.INITIALIZED;
|
||
+ return loader.plugin;
|
||
+ } catch (Throwable ex) {
|
||
+ this.status = ProviderStatus.ERRORED;
|
||
+ SneakyThrow.sneaky(ex);
|
||
+ }
|
||
+
|
||
+ throw new AssertionError(); // Shouldn't happen
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginDescriptionFile getMeta() {
|
||
+ return this.description;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Logger getLogger() {
|
||
+ return this.logger;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ProviderStatus getLastProvidedStatus() {
|
||
+ return this.status;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void setStatus(ProviderStatus status) {
|
||
+ this.status = status;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void setContext(DependencyContext context) {
|
||
+ this.dependencyContext = context;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "SpigotPluginProvider{" +
|
||
+ "path=" + path +
|
||
+ ", description=" + description +
|
||
+ ", jarFile=" + jarFile +
|
||
+ ", status=" + status +
|
||
+ ", dependencyContext=" + dependencyContext +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..14ed05945ba5bfeb2b539d4786278b0e04130404
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/provider/type/spigot/SpigotPluginProviderFactory.java
|
||
@@ -0,0 +1,45 @@
|
||
+package io.papermc.paper.plugin.provider.type.spigot;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.configuration.serializer.constraints.PluginConfigConstraints;
|
||
+import io.papermc.paper.plugin.provider.type.PluginTypeFactory;
|
||
+import org.bukkit.plugin.InvalidDescriptionException;
|
||
+import org.bukkit.plugin.PluginDescriptionFile;
|
||
+import org.yaml.snakeyaml.error.YAMLException;
|
||
+
|
||
+import java.io.IOException;
|
||
+import java.io.InputStream;
|
||
+import java.nio.file.Path;
|
||
+import java.util.Locale;
|
||
+import java.util.jar.JarEntry;
|
||
+import java.util.jar.JarFile;
|
||
+
|
||
+class SpigotPluginProviderFactory implements PluginTypeFactory<SpigotPluginProvider, PluginDescriptionFile> {
|
||
+
|
||
+ @Override
|
||
+ public SpigotPluginProvider build(JarFile file, PluginDescriptionFile configuration, Path source) throws Exception {
|
||
+ // Copied from SimplePluginManager#loadPlugins
|
||
+ // Spigot doesn't validate the name when the config is created, and instead when the plugin is loaded.
|
||
+ // Paper plugin configuration will do these checks in config serializer instead of when this is created.
|
||
+ String name = configuration.getRawName();
|
||
+ if (PluginConfigConstraints.RESERVED_KEYS.contains(name.toLowerCase(Locale.ROOT))) {
|
||
+ throw new InvalidDescriptionException("Restricted name, cannot use %s as a plugin name.".formatted(name));
|
||
+ } else if (name.indexOf(' ') != -1) {
|
||
+ throw new InvalidDescriptionException("Restricted name, cannot use 0x20 (space character) in a plugin name.");
|
||
+ }
|
||
+
|
||
+ return new SpigotPluginProvider(source, file, configuration);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginDescriptionFile create(JarFile file, JarEntry config) throws Exception {
|
||
+ PluginDescriptionFile descriptionFile;
|
||
+ try (InputStream inputStream = file.getInputStream(config)) {
|
||
+ descriptionFile = new PluginDescriptionFile(inputStream);
|
||
+ } catch (IOException | YAMLException ex) {
|
||
+ throw new InvalidDescriptionException(ex);
|
||
+ }
|
||
+
|
||
+ return descriptionFile;
|
||
+ }
|
||
+}
|
||
+
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..7af995b941ce83265a93cdc6b5a2de8ad27e4db6
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/storage/BootstrapProviderStorage.java
|
||
@@ -0,0 +1,113 @@
|
||
+package io.papermc.paper.plugin.storage;
|
||
+
|
||
+import com.mojang.logging.LogUtils;
|
||
+import io.papermc.paper.plugin.PluginInitializerManager;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginBootstrap;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginProviderContext;
|
||
+import io.papermc.paper.plugin.bootstrap.PluginProviderContextImpl;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatus;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatusHolder;
|
||
+import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta;
|
||
+import io.papermc.paper.plugin.provider.configuration.type.DependencyConfiguration;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+
|
||
+public class BootstrapProviderStorage extends SimpleProviderStorage<PluginBootstrap> {
|
||
+
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+
|
||
+ public BootstrapProviderStorage() {
|
||
+ super(new ModernPluginLoadingStrategy<>(new ProviderConfiguration<>() {
|
||
+ @Override
|
||
+ public void applyContext(PluginProvider<PluginBootstrap> provider, DependencyContext dependencyContext) {
|
||
+ if (provider instanceof DependencyContextHolder contextHolder) {
|
||
+ contextHolder.setContext(dependencyContext);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean load(PluginProvider<PluginBootstrap> provider, PluginBootstrap provided) {
|
||
+ try {
|
||
+ PluginProviderContext context = PluginProviderContextImpl.of(provider, PluginInitializerManager.instance().pluginDirectoryPath());
|
||
+ provided.bootstrap(context);
|
||
+ return true;
|
||
+ } catch (Exception e) {
|
||
+ LOGGER.error("Failed to run bootstrapper for %s. This plugin will not be loaded.".formatted(provider.getSource()), e);
|
||
+ if (provider instanceof ProviderStatusHolder statusHolder) {
|
||
+ statusHolder.setStatus(ProviderStatus.ERRORED);
|
||
+ }
|
||
+ return false;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> requiredDependencies(PluginProvider<PluginBootstrap> provider) {
|
||
+ List<String> dependencies = new ArrayList<>();
|
||
+ if (provider.getMeta() instanceof PaperPluginMeta paperPluginMeta) {
|
||
+ for (DependencyConfiguration configuration : paperPluginMeta.getDependencies()) {
|
||
+ if (configuration.required() && configuration.bootstrap()) {
|
||
+ dependencies.add(configuration.name());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return dependencies;
|
||
+ }
|
||
+
|
||
+ throw new IllegalStateException();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> optionalDependencies(PluginProvider<PluginBootstrap> provider) {
|
||
+ List<String> dependencies = new ArrayList<>();
|
||
+ if (provider.getMeta() instanceof PaperPluginMeta paperPluginMeta) {
|
||
+ for (DependencyConfiguration configuration : paperPluginMeta.getDependencies()) {
|
||
+ if (!configuration.required() && configuration.bootstrap()) {
|
||
+ dependencies.add(configuration.name());
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return dependencies;
|
||
+ }
|
||
+
|
||
+ throw new IllegalStateException();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> loadBeforeDependencies(PluginProvider<PluginBootstrap> provider) {
|
||
+ return provider.getMeta().getLoadBeforePlugins();
|
||
+ }
|
||
+ }));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ protected void handleCycle(PluginGraphCycleException exception) {
|
||
+ List<String> logMessages = new ArrayList<>();
|
||
+ for (List<String> list : exception.getCycles()) {
|
||
+ // CoolPlugin depends on Dependency depends on CoolPlugin...
|
||
+ logMessages.add(String.join(" depends on ", list) + " depends on " + list.get(0) + "...");
|
||
+ }
|
||
+
|
||
+ LOGGER.error("Circular dependencies detected!");
|
||
+ LOGGER.error("You have a plugin that is depending on a plugin which refers back to that plugin. Your server will shut down until these are resolved, or the strategy is changed.");
|
||
+ LOGGER.error("Circular dependencies:");
|
||
+ for (String message : logMessages) {
|
||
+ LOGGER.error(message);
|
||
+ }
|
||
+ LOGGER.error("If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true");
|
||
+
|
||
+ System.exit(-1);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "BOOTSTRAP:" + super.toString();
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..eb4a7e1fbd9f0e853ebf965c6b4f9e0e6061ad74
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/storage/ConfiguredProviderStorage.java
|
||
@@ -0,0 +1,51 @@
|
||
+package io.papermc.paper.plugin.storage;
|
||
+
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.LegacyPluginLoadingStrategy;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+import java.util.logging.Level;
|
||
+import java.util.logging.Logger;
|
||
+import java.util.stream.Collectors;
|
||
+
|
||
+public abstract class ConfiguredProviderStorage<T> extends SimpleProviderStorage<T> {
|
||
+
|
||
+ private static final Logger LOGGER = Logger.getLogger("ConfiguredOrderedProviderStorage");
|
||
+ public static final boolean LEGACY_PLUGIN_LOADING = Boolean.getBoolean("paper.useLegacyPluginLoading");
|
||
+
|
||
+ protected ConfiguredProviderStorage(ProviderConfiguration<T> onLoad) {
|
||
+ // This doesn't work with reloading.
|
||
+ // Should we care?
|
||
+ super(LEGACY_PLUGIN_LOADING ? new LegacyPluginLoadingStrategy<>(onLoad) : new ModernPluginLoadingStrategy<>(onLoad));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ protected void handleCycle(PluginGraphCycleException exception) {
|
||
+ List<String> logMessages = new ArrayList<>();
|
||
+ for (List<String> list : exception.getCycles()) {
|
||
+ logMessages.add(String.join(" -> ", list) + " -> " + list.get(0));
|
||
+ }
|
||
+
|
||
+ LOGGER.log(Level.SEVERE, "Circular dependencies detected! This happens when");
|
||
+ LOGGER.log(Level.SEVERE, " i) plugin A has a plugin B in its (soft)depend list, and plugin B has plugin A in its (soft)depend list, or");
|
||
+ LOGGER.log(Level.SEVERE, " ii) plugin A has plugin B both in its (soft)depend list and its loadbefore list.");
|
||
+ LOGGER.log(Level.SEVERE, "Circular dependencies:");
|
||
+ for (String logMessage : logMessages) {
|
||
+ LOGGER.log(Level.SEVERE, " " + logMessage);
|
||
+ }
|
||
+ LOGGER.log(Level.SEVERE, "Please report this to the plugin authors of the first plugin of each loop or join the PaperMC Discord server for further help.");
|
||
+ LOGGER.log(Level.SEVERE, "If you would like to still load these plugins, acknowledging that there may be unexpected plugin loading issues, run the server with -Dpaper.useLegacyPluginLoading=true");
|
||
+
|
||
+ if (this.exitOnCycleDependencies()) {
|
||
+ throw new IllegalStateException("Circular plugin dependencies from plugins " + exception.getCycles().stream().map(cycle -> cycle.get(0)).collect(Collectors.joining(", ")));
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public boolean exitOnCycleDependencies() {
|
||
+ return true;
|
||
+ }
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..374e7d3d69fc8603ecf54999f173123d3a9fbf6e
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/storage/ProviderStorage.java
|
||
@@ -0,0 +1,18 @@
|
||
+package io.papermc.paper.plugin.storage;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+
|
||
+/**
|
||
+ * A provider storage is meant to be a singleton that stores providers.
|
||
+ *
|
||
+ * @param <T> provider type
|
||
+ */
|
||
+public interface ProviderStorage<T> {
|
||
+
|
||
+ void register(PluginProvider<T> provider);
|
||
+
|
||
+ void enter();
|
||
+
|
||
+ Iterable<PluginProvider<T>> getRegisteredProviders();
|
||
+
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/storage/ServerPluginProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/ServerPluginProviderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..b049691292fb9d53ac598cb333fa408b043e7bc5
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/storage/ServerPluginProviderStorage.java
|
||
@@ -0,0 +1,82 @@
|
||
+package io.papermc.paper.plugin.storage;
|
||
+
|
||
+import com.mojang.logging.LogUtils;
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.entrypoint.dependency.DependencyContextHolder;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration;
|
||
+import io.papermc.paper.plugin.manager.PaperPluginManagerImpl;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatus;
|
||
+import io.papermc.paper.plugin.provider.ProviderStatusHolder;
|
||
+import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent;
|
||
+import org.bukkit.plugin.Plugin;
|
||
+import org.bukkit.plugin.java.JavaPlugin;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+import java.util.List;
|
||
+
|
||
+public class ServerPluginProviderStorage extends ConfiguredProviderStorage<JavaPlugin> {
|
||
+
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+
|
||
+ public ServerPluginProviderStorage() {
|
||
+ super(new ProviderConfiguration<>() {
|
||
+ @Override
|
||
+ public void applyContext(PluginProvider<JavaPlugin> provider, DependencyContext dependencyContext) {
|
||
+ Plugin alreadyLoadedPlugin = PaperPluginManagerImpl.getInstance().getPlugin(provider.getMeta().getName());
|
||
+ if (alreadyLoadedPlugin != null) {
|
||
+ throw new IllegalStateException("Provider " + provider + " attempted to add duplicate plugin identifier " + alreadyLoadedPlugin + " THIS WILL CREATE BUGS!!!");
|
||
+ }
|
||
+
|
||
+ if (provider instanceof DependencyContextHolder contextHolder) {
|
||
+ contextHolder.setContext(dependencyContext);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean load(PluginProvider<JavaPlugin> provider, JavaPlugin provided) {
|
||
+ try {
|
||
+ provided.getLogger().info(String.format("Loading server plugin %s", provided.getPluginMeta().getDisplayName()));
|
||
+ PaperPluginManagerImpl.getInstance().loadPlugin(provided); // We have to add it to the map before the plugin is loaded
|
||
+ provided.onLoad();
|
||
+ return true;
|
||
+ } catch (Throwable ex) {
|
||
+ if (provider instanceof ProviderStatusHolder statusHolder) {
|
||
+ statusHolder.setStatus(ProviderStatus.ERRORED);
|
||
+ }
|
||
+ LOGGER.error("Could not load server plugin '%s' in folder '%s' (Is it up to date?)".formatted(provider.getFileName(), provider.getParentSource()), ex);
|
||
+ return false;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> requiredDependencies(PluginProvider<JavaPlugin> provider) {
|
||
+ return provider.getMeta().getPluginDependencies();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> optionalDependencies(PluginProvider<JavaPlugin> provider) {
|
||
+ return provider.getMeta().getPluginSoftDependencies();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> loadBeforeDependencies(PluginProvider<JavaPlugin> provider) {
|
||
+ return provider.getMeta().getLoadBeforePlugins();
|
||
+ }
|
||
+ });
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ protected void filterLoadingProviders(List<PluginProvider<JavaPlugin>> pluginProviders) {
|
||
+ /*
|
||
+ Have to do this to prevent loading plugin providers that have failed initializers.
|
||
+ This is a hack and a better solution here would be to store failed plugin providers elsewhere.
|
||
+ */
|
||
+ pluginProviders.removeIf((provider) -> (provider instanceof PaperPluginParent.PaperServerPluginProvider pluginProvider && pluginProvider.shouldSkipCreation()));
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "PLUGIN:" + super.toString();
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java b/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..ac263f02a9a79fb3db24c4daa16c405f9b0f7459
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/storage/SimpleProviderStorage.java
|
||
@@ -0,0 +1,57 @@
|
||
+package io.papermc.paper.plugin.storage;
|
||
+
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.PluginGraphCycleException;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ProviderLoadingStrategy;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.List;
|
||
+
|
||
+public abstract class SimpleProviderStorage<T> implements ProviderStorage<T> {
|
||
+
|
||
+ protected final List<PluginProvider<T>> providers = new ArrayList<>();
|
||
+ protected ProviderLoadingStrategy<T> strategy;
|
||
+
|
||
+ protected SimpleProviderStorage(ProviderLoadingStrategy<T> strategy) {
|
||
+ this.strategy = strategy;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void register(PluginProvider<T> provider) {
|
||
+ this.providers.add(provider);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void enter() {
|
||
+ List<PluginProvider<T>> providerList = new ArrayList<>(this.providers);
|
||
+ this.filterLoadingProviders(providerList);
|
||
+
|
||
+ try {
|
||
+ for (T plugin : this.strategy.loadProviders(providerList)) {
|
||
+ this.processProvided(plugin);
|
||
+ }
|
||
+ } catch (PluginGraphCycleException exception) {
|
||
+ this.handleCycle(exception);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Iterable<PluginProvider<T>> getRegisteredProviders() {
|
||
+ return this.providers;
|
||
+ }
|
||
+
|
||
+ public void processProvided(T provided) {}
|
||
+
|
||
+ // Mutable enter
|
||
+ protected void filterLoadingProviders(List<PluginProvider<T>> providers) {}
|
||
+
|
||
+ protected abstract void handleCycle(PluginGraphCycleException exception);
|
||
+
|
||
+ @Override
|
||
+ public String toString() {
|
||
+ return "SimpleProviderStorage{" +
|
||
+ "providers=" + this.providers +
|
||
+ ", strategy=" + this.strategy +
|
||
+ '}';
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/storage/package-info.java b/src/main/java/io/papermc/paper/plugin/storage/package-info.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..c1114675137e862ac9682b635bfdbfbc1d7c6e67
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/storage/package-info.java
|
||
@@ -0,0 +1,5 @@
|
||
+/**
|
||
+ * Classes in this package are supposed to connect components of {@link io.papermc.paper.plugin.entrypoint} and {@link io.papermc.paper.plugin.provider} packages.
|
||
+ * @see io.papermc.paper.plugin.entrypoint.Entrypoint
|
||
+ */
|
||
+package io.papermc.paper.plugin.storage;
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/util/EntrypointUtil.java b/src/main/java/io/papermc/paper/plugin/util/EntrypointUtil.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..98518351e616e536315cd89790b327d3bad33d0e
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/util/EntrypointUtil.java
|
||
@@ -0,0 +1,19 @@
|
||
+package io.papermc.paper.plugin.util;
|
||
+
|
||
+import com.mojang.logging.LogUtils;
|
||
+import io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler;
|
||
+import io.papermc.paper.plugin.provider.source.ProviderSource;
|
||
+import org.slf4j.Logger;
|
||
+
|
||
+public class EntrypointUtil {
|
||
+
|
||
+ private static final Logger LOGGER = LogUtils.getLogger();
|
||
+
|
||
+ public static <C> void registerProvidersFromSource(ProviderSource<C> source, C context) {
|
||
+ try {
|
||
+ source.registerProviders(LaunchEntryPointHandler.INSTANCE, context);
|
||
+ } catch (Throwable e) {
|
||
+ LOGGER.error(e.getMessage(), e);
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/plugin/util/NamespaceChecker.java b/src/main/java/io/papermc/paper/plugin/util/NamespaceChecker.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..fd55fd1d6518ebd1bc2513dd331f072018fd4782
|
||
--- /dev/null
|
||
+++ b/src/main/java/io/papermc/paper/plugin/util/NamespaceChecker.java
|
||
@@ -0,0 +1,37 @@
|
||
+package io.papermc.paper.plugin.util;
|
||
+
|
||
+import org.jetbrains.annotations.ApiStatus;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+@ApiStatus.Internal
|
||
+public class NamespaceChecker {
|
||
+
|
||
+ private static final String[] QUICK_INVALID_NAMESPACES = {
|
||
+ "net.minecraft.",
|
||
+ "org.bukkit.",
|
||
+ "io.papermc.paper.",
|
||
+ "com.destroystokoyo.paper."
|
||
+ };
|
||
+
|
||
+ /**
|
||
+ * Used for a variety of namespaces that shouldn't be resolved and should instead be moved to
|
||
+ * other classloaders. We can assume this because only plugins should be using this classloader.
|
||
+ *
|
||
+ * @param name namespace
|
||
+ */
|
||
+ public static void validateNameSpaceForClassloading(@NotNull String name) throws ClassNotFoundException {
|
||
+ if (!isValidNameSpace(name)) {
|
||
+ throw new ClassNotFoundException(name);
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public static boolean isValidNameSpace(@NotNull String name) {
|
||
+ for (String string : QUICK_INVALID_NAMESPACES) {
|
||
+ if (name.startsWith(string)) {
|
||
+ return false;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ return true;
|
||
+ }
|
||
+}
|
||
diff --git a/src/main/java/io/papermc/paper/util/StackWalkerUtil.java b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java
|
||
index f7114d5b8f2f93f62883e24da29afaf9f74ee1a6..00e78a495575abb9496b1849822605b613afe3b4 100644
|
||
--- a/src/main/java/io/papermc/paper/util/StackWalkerUtil.java
|
||
+++ b/src/main/java/io/papermc/paper/util/StackWalkerUtil.java
|
||
@@ -1,9 +1,11 @@
|
||
package io.papermc.paper.util;
|
||
|
||
+import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader;
|
||
import org.bukkit.plugin.java.JavaPlugin;
|
||
import org.bukkit.plugin.java.PluginClassLoader;
|
||
import org.jetbrains.annotations.Nullable;
|
||
|
||
+import java.util.Objects;
|
||
import java.util.Optional;
|
||
|
||
public class StackWalkerUtil {
|
||
@@ -12,11 +14,20 @@ public class StackWalkerUtil {
|
||
public static JavaPlugin getFirstPluginCaller() {
|
||
Optional<JavaPlugin> foundFrame = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
|
||
.walk(stream -> stream
|
||
- .filter(frame -> frame.getDeclaringClass().getClassLoader() instanceof PluginClassLoader)
|
||
.map((frame) -> {
|
||
- PluginClassLoader classLoader = (PluginClassLoader) frame.getDeclaringClass().getClassLoader();
|
||
- return classLoader.getPlugin();
|
||
+ ClassLoader classLoader = frame.getDeclaringClass().getClassLoader();
|
||
+ JavaPlugin plugin;
|
||
+ if (classLoader instanceof PaperPluginClassLoader pluginClassLoader) {
|
||
+ plugin = pluginClassLoader.getLoadedJavaPlugin();
|
||
+ } else if (classLoader instanceof PluginClassLoader spigotClassloader) {
|
||
+ plugin = spigotClassloader.getPlugin();
|
||
+ } else {
|
||
+ plugin = null;
|
||
+ }
|
||
+
|
||
+ return plugin;
|
||
})
|
||
+ .filter(Objects::nonNull)
|
||
.findFirst());
|
||
|
||
return foundFrame.orElse(null);
|
||
diff --git a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
|
||
index 5b8ecf5b0165ed2cd4397cdee958e97c2e8f18d5..ad802eb211f05f646159d7fc53f8a9427b46cb89 100644
|
||
--- a/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
|
||
+++ b/src/main/java/net/minecraft/core/registries/BuiltInRegistries.java
|
||
@@ -310,7 +310,13 @@ public class BuiltInRegistries {
|
||
}
|
||
|
||
public static void bootStrap() {
|
||
+ // Paper start
|
||
+ bootStrap(() -> {});
|
||
+ }
|
||
+ public static void bootStrap(Runnable runnable) {
|
||
+ // Paper end
|
||
createContents();
|
||
+ runnable.run(); // Paper
|
||
freeze();
|
||
validate(REGISTRY);
|
||
}
|
||
diff --git a/src/main/java/net/minecraft/server/Bootstrap.java b/src/main/java/net/minecraft/server/Bootstrap.java
|
||
index b5aa358638b9d0638dfe47f7ebac04cca1dd80b9..e43096e69a00f9ea96badd7c966443cfcf3e7b95 100644
|
||
--- a/src/main/java/net/minecraft/server/Bootstrap.java
|
||
+++ b/src/main/java/net/minecraft/server/Bootstrap.java
|
||
@@ -69,7 +69,11 @@ public class Bootstrap {
|
||
EntitySelectorOptions.bootStrap();
|
||
DispenseItemBehavior.bootStrap();
|
||
CauldronInteraction.bootStrap();
|
||
- BuiltInRegistries.bootStrap();
|
||
+ // Paper start
|
||
+ BuiltInRegistries.bootStrap(() -> {
|
||
+ io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.enterBootstrappers(); // Paper - Entrypoint for bootstrapping
|
||
+ });
|
||
+ // Paper end
|
||
Bootstrap.wrapStreams();
|
||
}
|
||
// CraftBukkit start - easier than fixing the decompile
|
||
diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java
|
||
index b7399e29094c66c88a6f4c0e996a906bcaa3b4ca..abf4c54eec6881d6e05893983f83f9eb4b249634 100644
|
||
--- a/src/main/java/net/minecraft/server/Main.java
|
||
+++ b/src/main/java/net/minecraft/server/Main.java
|
||
@@ -110,6 +110,17 @@ public class Main {
|
||
JvmProfiler.INSTANCE.start(Environment.SERVER);
|
||
}
|
||
|
||
+ // Paper start
|
||
+
|
||
+ // We have to load the bukkit configuration inorder to get the update folder location.
|
||
+ io.papermc.paper.plugin.PluginInitializerManager pluginSystem = io.papermc.paper.plugin.PluginInitializerManager.init(optionset);
|
||
+ // Register the default plugin directory
|
||
+ io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.DirectoryProviderSource.INSTANCE, pluginSystem.pluginDirectoryPath());
|
||
+ @SuppressWarnings("unchecked")
|
||
+ java.util.List<File> files = (java.util.List<File>) optionset.valuesOf("add-plugin");
|
||
+ // Register plugins from the flag
|
||
+ io.papermc.paper.plugin.util.EntrypointUtil.registerProvidersFromSource(io.papermc.paper.plugin.provider.source.PluginFlagProviderSource.INSTANCE, files);
|
||
+ // Paper end
|
||
Bootstrap.bootStrap();
|
||
Bootstrap.validate();
|
||
Util.startTimerHackThread();
|
||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||
index 26ca07b5e302cc4cc02e06f5d07f6d9eb541275e..17a6290969a63be85fa780e2cad4ce63790379b1 100644
|
||
--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java
|
||
@@ -268,7 +268,8 @@ public final class CraftServer implements Server {
|
||
private final CraftCommandMap commandMap = new CraftCommandMap(this);
|
||
private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
|
||
private final StandardMessenger messenger = new StandardMessenger();
|
||
- private final SimplePluginManager pluginManager = new SimplePluginManager(this, this.commandMap);
|
||
+ private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap);
|
||
+ public final io.papermc.paper.plugin.manager.PaperPluginManagerImpl paperPluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(this, this.commandMap, pluginManager); {this.pluginManager.paperPluginManager = this.paperPluginManager;} // Paper
|
||
private final StructureManager structureManager;
|
||
protected final DedicatedServer console;
|
||
protected final DedicatedPlayerList playerList;
|
||
@@ -416,24 +417,7 @@ public final class CraftServer implements Server {
|
||
}
|
||
|
||
public void loadPlugins() {
|
||
- this.pluginManager.registerInterface(JavaPluginLoader.class);
|
||
-
|
||
- File pluginFolder = (File) console.options.valueOf("plugins");
|
||
-
|
||
- if (pluginFolder.exists()) {
|
||
- Plugin[] plugins = this.pluginManager.loadPlugins(pluginFolder);
|
||
- for (Plugin plugin : plugins) {
|
||
- try {
|
||
- String message = String.format("Loading %s", plugin.getDescription().getFullName());
|
||
- plugin.getLogger().info(message);
|
||
- plugin.onLoad();
|
||
- } catch (Throwable ex) {
|
||
- Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, ex.getMessage() + " initializing " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
|
||
- }
|
||
- }
|
||
- } else {
|
||
- pluginFolder.mkdir();
|
||
- }
|
||
+ io.papermc.paper.plugin.entrypoint.LaunchEntryPointHandler.INSTANCE.enter(io.papermc.paper.plugin.entrypoint.Entrypoint.PLUGIN); // Paper - replace implementation
|
||
}
|
||
|
||
public void enablePlugins(PluginLoadOrder type) {
|
||
@@ -522,15 +506,17 @@ public final class CraftServer implements Server {
|
||
private void enablePlugin(Plugin plugin) {
|
||
try {
|
||
List<Permission> perms = plugin.getDescription().getPermissions();
|
||
-
|
||
+ List<Permission> permsToLoad = new ArrayList<>(); // Paper
|
||
for (Permission perm : perms) {
|
||
- try {
|
||
- this.pluginManager.addPermission(perm, false);
|
||
- } catch (IllegalArgumentException ex) {
|
||
- this.getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex);
|
||
+ // Paper start
|
||
+ if (this.paperPluginManager.getPermission(perm.getName()) == null) {
|
||
+ permsToLoad.add(perm);
|
||
+ } else {
|
||
+ this.getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered");
|
||
+ // Paper end
|
||
}
|
||
}
|
||
- this.pluginManager.dirtyPermissibles();
|
||
+ this.paperPluginManager.addPermissions(permsToLoad); // Paper
|
||
|
||
this.pluginManager.enablePlugin(plugin);
|
||
} catch (Throwable ex) {
|
||
diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
||
index 909b2c98e7a9117d2f737245e4661792ffafb744..d96399e9bf1a58db5a4a22e58abb99e7660e0694 100644
|
||
--- a/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
||
+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/MinecraftInternalPlugin.java
|
||
@@ -42,6 +42,12 @@ public class MinecraftInternalPlugin extends PluginBase {
|
||
public PluginDescriptionFile getDescription() {
|
||
return pdf;
|
||
}
|
||
+ // Paper start
|
||
+ @Override
|
||
+ public io.papermc.paper.plugin.configuration.PluginMeta getPluginMeta() {
|
||
+ return pdf;
|
||
+ }
|
||
+ // Paper end
|
||
|
||
@Override
|
||
public FileConfiguration getConfig() {
|
||
diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
||
index 750f4b3930278c291f10015c7a8a8df57d04a286..3d2303dbd06a12968302cb100e36be9de28700f0 100644
|
||
--- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
||
+++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java
|
||
@@ -436,6 +436,12 @@ public final class CraftMagicNumbers implements UnsafeValues {
|
||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||
return nmsItemStack.getItem().getDescriptionId(nmsItemStack);
|
||
}
|
||
+ // Paper start
|
||
+ @Override
|
||
+ public boolean isSupportedApiVersion(String apiVersion) {
|
||
+ return apiVersion != null && SUPPORTED_API.contains(apiVersion);
|
||
+ }
|
||
+ // Paper end
|
||
|
||
/**
|
||
* This helper class represents the different NBT Tags.
|
||
diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier b/src/main/resources/META-INF/services/io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..20dbe2775951bfcdb85c5d679ac86c77a93e0847
|
||
--- /dev/null
|
||
+++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.entrypoint.classloader.ClassloaderBytecodeModifier
|
||
@@ -0,0 +1 @@
|
||
+io.papermc.paper.plugin.entrypoint.classloader.PaperClassloaderBytecodeModifier
|
||
diff --git a/src/main/resources/META-INF/services/io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage b/src/main/resources/META-INF/services/io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..a22647244037cd92262b3b5a6582f0a11172fdc8
|
||
--- /dev/null
|
||
+++ b/src/main/resources/META-INF/services/io.papermc.paper.plugin.provider.classloader.PaperClassLoaderStorage
|
||
@@ -0,0 +1 @@
|
||
+io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoaderStorage
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..1d14f530ef888102e47eeeaf0d1a6076e51871c4
|
||
--- /dev/null
|
||
+++ b/src/test/java/io/papermc/paper/plugin/PaperTestPlugin.java
|
||
@@ -0,0 +1,146 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import org.bukkit.Server;
|
||
+import org.bukkit.command.Command;
|
||
+import org.bukkit.command.CommandSender;
|
||
+import org.bukkit.configuration.file.FileConfiguration;
|
||
+import org.bukkit.generator.BiomeProvider;
|
||
+import org.bukkit.generator.ChunkGenerator;
|
||
+import org.bukkit.plugin.PluginBase;
|
||
+import org.bukkit.plugin.PluginDescriptionFile;
|
||
+import org.bukkit.plugin.PluginLoader;
|
||
+import org.bukkit.plugin.PluginLogger;
|
||
+
|
||
+import java.io.File;
|
||
+import java.io.InputStream;
|
||
+import java.util.List;
|
||
+
|
||
+public class PaperTestPlugin extends PluginBase {
|
||
+ private final String pluginName;
|
||
+ private boolean enabled = true;
|
||
+ private final PluginMeta configuration;
|
||
+
|
||
+ public PaperTestPlugin(String pluginName) {
|
||
+ this.pluginName = pluginName;
|
||
+ this.configuration = new TestPluginMeta(pluginName);
|
||
+ }
|
||
+
|
||
+ public PaperTestPlugin(PluginMeta configuration) {
|
||
+ this.configuration = configuration;
|
||
+ this.pluginName = configuration.getName();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public File getDataFolder() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginDescriptionFile getDescription() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginMeta getPluginMeta() {
|
||
+ return this.configuration;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public FileConfiguration getConfig() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public InputStream getResource(String filename) {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void saveConfig() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void saveDefaultConfig() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void saveResource(String resourcePath, boolean replace) {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void reloadConfig() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginLogger getLogger() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PluginLoader getPluginLoader() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Server getServer() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean isEnabled() {
|
||
+ return enabled;
|
||
+ }
|
||
+
|
||
+ public void setEnabled(boolean enabled) {
|
||
+ this.enabled = enabled;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void onDisable() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void onLoad() {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void onEnable() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean isNaggable() {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public void setNaggable(boolean canNag) {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public ChunkGenerator getDefaultWorldGenerator(String worldName, String id) {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public BiomeProvider getDefaultBiomeProvider(String worldName, String id) {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||
+ throw new UnsupportedOperationException("Not supported.");
|
||
+ }
|
||
+}
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java b/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..7665967dc1849dde750c6d24298e76c2c74f8443
|
||
--- /dev/null
|
||
+++ b/src/test/java/io/papermc/paper/plugin/PluginDependencyLoadingTest.java
|
||
@@ -0,0 +1,159 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.entrypoint.DependencyContext;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ModernPluginLoadingStrategy;
|
||
+import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration;
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import org.junit.Assert;
|
||
+import org.junit.Before;
|
||
+import org.junit.Test;
|
||
+
|
||
+import java.util.ArrayList;
|
||
+import java.util.HashMap;
|
||
+import java.util.List;
|
||
+import java.util.Map;
|
||
+import java.util.concurrent.atomic.AtomicInteger;
|
||
+
|
||
+public class PluginDependencyLoadingTest {
|
||
+
|
||
+ private static List<PluginProvider<PaperTestPlugin>> REGISTERED_PROVIDERS = new ArrayList<>();
|
||
+ private static Map<String, Integer> LOAD_ORDER = new HashMap<>();
|
||
+
|
||
+ static {
|
||
+ setup();
|
||
+ }
|
||
+
|
||
+ private static TestJavaPluginProvider setup(String identifier, String[] hard, String[] soft, String[] before) {
|
||
+ TestPluginMeta configuration = new TestPluginMeta(identifier);
|
||
+ configuration.setHardDependencies(List.of(hard));
|
||
+ configuration.setSoftDependencies(List.of(soft));
|
||
+ configuration.setLoadBefore(List.of(before));
|
||
+
|
||
+ TestJavaPluginProvider provider = new TestJavaPluginProvider(configuration);
|
||
+ REGISTERED_PROVIDERS.add(provider);
|
||
+ return provider;
|
||
+ }
|
||
+
|
||
+ /**
|
||
+ * Obfuscated plugin names, this uses a real dependency tree...
|
||
+ */
|
||
+ private static void setup() {
|
||
+ setup("RedAir", new String[]{}, new String[]{"NightShovel", "EmeraldFire"}, new String[]{"GreenShovel", "IronSpork", "BrightBlueShovel", "WireDoor"});
|
||
+ setup("BigGrass", new String[]{}, new String[]{"IronEarth", "RedAir"}, new String[]{"BlueFire"});
|
||
+ setup("BlueFire", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("BigPaper", new String[]{}, new String[]{"BlueFire"}, new String[]{});
|
||
+ setup("EmeraldSpork", new String[]{}, new String[]{}, new String[]{"GoldPaper", "YellowSnow"});
|
||
+ setup("GreenShovel", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("BrightBlueGrass", new String[]{"BigPaper"}, new String[]{"DarkSpork"}, new String[]{});
|
||
+ setup("GoldPaper", new String[]{}, new String[]{"BlueFire"}, new String[]{});
|
||
+ setup("GreenGlass", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("GoldNeptune", new String[]{}, new String[]{"GreenShovel", "GoldNeptuneVersioning"}, new String[]{});
|
||
+ setup("RedPaper", new String[]{}, new String[]{"GoldPaper", "GoldFire", "EmeraldGrass", "BlueFire", "CopperSpork", "YellowDoor", "OrangeClam", "BlueSponge", "GoldNeptune", "BrightBlueGrass", "DarkSpoon", "BigShovel", "GreenGlass", "IronGlass"}, new String[]{"IronPaper", "YellowFire"});
|
||
+ setup("YellowGrass", new String[]{}, new String[]{"RedAir"}, new String[]{});
|
||
+ setup("WireFire", new String[]{}, new String[]{"RedPaper", "WireGrass", "YellowSpork", "NightAir"}, new String[]{});
|
||
+ setup("OrangeNeptune", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("BigSpoon", new String[]{"YellowGrass", "GreenShovel"}, new String[]{"RedAir", "GoldNeptune", "BrightBlueGrass", "LightDoor", "LightSpork", "LightEarth", "NightDoor", "OrangeSpoon", "GoldSponge", "GoldDoor", "DarkPaper", "RedPaper", "GreenGlass", "IronGlass", "NightGlass", "BigGrass", "BlueFire", "YellowSpoon", "DiamondGrass", "DiamondShovel", "DarkSnow", "EmeraldGlass", "EmeraldSpoon", "LightFire", "WireGrass", "RedEarth", "WireFire"}, new String[]{});
|
||
+ setup("CopperSnow", new String[]{}, new String[]{"RedSnow", "OrangeFire", "WireAir", "GreenGlass", "NightSpork", "EmeraldPaper"}, new String[]{"BlueGrass"});
|
||
+ setup("BrightBluePaper", new String[]{}, new String[]{"GoldEarth", "BrightBlueSpoon", "CopperGlass", "LightSporkChat", "DarkAir", "LightEarth", "DiamondDoor", "YellowShovel", "BlueAir", "DarkShovel", "GoldPaper", "BlueFire", "GreenGlass", "YellowSpork", "BigGrass", "OrangePaper", "DarkPaper"}, new String[]{"WireShovel"});
|
||
+ setup("LightSponge", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("OrangeShovel", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("GoldGrass", new String[]{}, new String[]{"GreenGlass", "BlueFire"}, new String[]{});
|
||
+ setup("IronSponge", new String[]{}, new String[]{"DiamondEarth"}, new String[]{});
|
||
+ setup("EmeraldSnow", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("BlueSpoon", new String[]{"BigGrass"}, new String[]{"GreenGlass", "GoldPaper", "GreenShovel", "YellowClam"}, new String[]{});
|
||
+ setup("BigSpork", new String[]{}, new String[]{"BigPaper"}, new String[]{});
|
||
+ setup("BluePaper", new String[]{}, new String[]{"BigClam", "RedSpoon", "GreenFire", "WireSnow", "OrangeSnow", "BlueFire", "BrightBlueGrass", "YellowSpork", "GreenGlass"}, new String[]{});
|
||
+ setup("OrangeSpork", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("DiamondNeptune", new String[]{}, new String[]{"GreenGlass", "GreenShovel", "YellowNeptune"}, new String[]{});
|
||
+ setup("BigFire", new String[]{}, new String[]{"BlueFire", "BrightBlueDoor", "GreenGlass"}, new String[]{});
|
||
+ setup("NightNeptune", new String[]{}, new String[]{"BlueFire", "DarkGlass", "GoldPaper", "YellowNeptune", "BlueShovel"}, new String[]{});
|
||
+ setup("YellowEarth", new String[]{"RedAir"}, new String[]{}, new String[]{});
|
||
+ setup("DiamondClam", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("CopperAir", new String[]{}, new String[]{"BigPaper"}, new String[]{});
|
||
+ setup("NightSpoon", new String[]{"OrangeNeptune"}, new String[]{"BlueFire", "GreenGlass", "RedSpork", "GoldPaper", "BigShovel", "YellowSponge", "EmeraldSpork"}, new String[]{});
|
||
+ setup("GreenClam", new String[]{}, new String[]{"GreenShovel", "BrightBlueEarth", "BigSpoon", "RedPaper", "BlueFire", "GreenGlass", "WireFire", "GreenSnow"}, new String[]{});
|
||
+ setup("YellowPaper", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("WireGlass", new String[]{"YellowGrass"}, new String[]{"YellowGlass", "BigSpoon", "CopperSnow", "GreenGlass", "BlueEarth"}, new String[]{});
|
||
+ setup("BlueSpork", new String[]{}, new String[]{"BrightBlueGrass"}, new String[]{});
|
||
+ setup("CopperShovel", new String[]{}, new String[]{"GreenGlass"}, new String[]{});
|
||
+ setup("RedClam", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("EmeraldClam", new String[]{}, new String[]{"BlueFire"}, new String[]{});
|
||
+ setup("DarkClam", new String[]{}, new String[]{"GoldAir", "LightGlass"}, new String[]{});
|
||
+ setup("WireSpoon", new String[]{}, new String[]{"GoldPaper", "LightSnow"}, new String[]{});
|
||
+ setup("CopperNeptune", new String[]{}, new String[]{"GreenGlass", "BigGrass"}, new String[]{});
|
||
+ setup("RedNeptune", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("GreenAir", new String[]{}, new String[]{}, new String[]{});
|
||
+ setup("RedFire", new String[]{"BrightBlueGrass", "BigPaper"}, new String[]{"BlueFire", "GreenGlass", "BigGrass"}, new String[]{});
|
||
+ }
|
||
+
|
||
+ @Before
|
||
+ public void loadProviders() {
|
||
+ AtomicInteger currentLoad = new AtomicInteger();
|
||
+ ModernPluginLoadingStrategy<PaperTestPlugin> modernPluginLoadingStrategy = new ModernPluginLoadingStrategy<>(new ProviderConfiguration<>() {
|
||
+ @Override
|
||
+ public void applyContext(PluginProvider<PaperTestPlugin> provider, DependencyContext dependencyContext) {
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public boolean load(PluginProvider<PaperTestPlugin> provider, PaperTestPlugin provided) {
|
||
+ LOAD_ORDER.put(provider.getMeta().getName(), currentLoad.getAndIncrement());
|
||
+ return false;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> requiredDependencies(PluginProvider<PaperTestPlugin> provider) {
|
||
+ return provider.getMeta().getPluginDependencies();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> optionalDependencies(PluginProvider<PaperTestPlugin> provider) {
|
||
+ return provider.getMeta().getPluginSoftDependencies();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public List<String> loadBeforeDependencies(PluginProvider<PaperTestPlugin> provider) {
|
||
+ return provider.getMeta().getLoadBeforePlugins();
|
||
+ }
|
||
+ });
|
||
+
|
||
+ modernPluginLoadingStrategy.loadProviders(REGISTERED_PROVIDERS);
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testDependencies() {
|
||
+ for (PluginProvider<PaperTestPlugin> provider : REGISTERED_PROVIDERS) {
|
||
+ TestPluginMeta pluginMeta = (TestPluginMeta) provider.getMeta();
|
||
+ String identifier = pluginMeta.getName();
|
||
+ Assert.assertTrue("Provider wasn't loaded! (%s)".formatted(identifier), LOAD_ORDER.containsKey(identifier));
|
||
+
|
||
+ int index = LOAD_ORDER.get(identifier);
|
||
+
|
||
+ // Hard dependencies should be loaded BEFORE
|
||
+ for (String hardDependency : pluginMeta.getPluginDependencies()) {
|
||
+ Assert.assertTrue("Plugin (%s) is missing hard dependency (%s)".formatted(identifier, hardDependency), LOAD_ORDER.containsKey(hardDependency));
|
||
+
|
||
+ int dependencyIndex = LOAD_ORDER.get(hardDependency);
|
||
+ Assert.assertTrue("Plugin (%s) was not loaded BEFORE soft dependency. (%s)".formatted(identifier, hardDependency), index > dependencyIndex);
|
||
+ }
|
||
+
|
||
+ for (String softDependency : pluginMeta.getPluginSoftDependencies()) {
|
||
+ if (!LOAD_ORDER.containsKey(softDependency)) {
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ int dependencyIndex = LOAD_ORDER.get(softDependency);
|
||
+
|
||
+ Assert.assertTrue("Plugin (%s) was not loaded BEFORE soft dependency. (%s)".formatted(identifier, softDependency), index > dependencyIndex);
|
||
+ }
|
||
+
|
||
+ for (String loadBefore : pluginMeta.getLoadBeforePlugins()) {
|
||
+ if (!LOAD_ORDER.containsKey(loadBefore)) {
|
||
+ continue;
|
||
+ }
|
||
+
|
||
+ int dependencyIndex = LOAD_ORDER.get(loadBefore);
|
||
+ Assert.assertTrue("Plugin (%s) was NOT loaded BEFORE loadbefore dependency. (%s)".formatted(identifier, loadBefore), index < dependencyIndex);
|
||
+ }
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/PluginLoadingTest.java b/src/test/java/io/papermc/paper/plugin/PluginLoadingTest.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java b/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..726eba26470e62b0e94a91418512e242464800ae
|
||
--- /dev/null
|
||
+++ b/src/test/java/io/papermc/paper/plugin/PluginManagerTest.java
|
||
@@ -0,0 +1,75 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.event.Event;
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.plugin.PluginManager;
|
||
+import org.bukkit.support.AbstractTestingBase;
|
||
+import org.junit.After;
|
||
+import org.junit.Test;
|
||
+
|
||
+import static org.hamcrest.MatcherAssert.assertThat;
|
||
+import static org.hamcrest.Matchers.*;
|
||
+
|
||
+public class PluginManagerTest extends AbstractTestingBase {
|
||
+
|
||
+ private static final PluginManager pm = Bukkit.getPluginManager();
|
||
+
|
||
+ @Test
|
||
+ public void testSyncSameThread() {
|
||
+ final Event event = new TestEvent(false);
|
||
+ pm.callEvent(event);
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testRemovePermissionByNameLower() {
|
||
+ this.testRemovePermissionByName("lower");
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testRemovePermissionByNameUpper() {
|
||
+ this.testRemovePermissionByName("UPPER");
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testRemovePermissionByNameCamel() {
|
||
+ this.testRemovePermissionByName("CaMeL");
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testRemovePermissionByPermissionLower() {
|
||
+ this.testRemovePermissionByPermission("lower");
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testRemovePermissionByPermissionUpper() {
|
||
+ this.testRemovePermissionByPermission("UPPER");
|
||
+ }
|
||
+
|
||
+ @Test
|
||
+ public void testRemovePermissionByPermissionCamel() {
|
||
+ this.testRemovePermissionByPermission("CaMeL");
|
||
+ }
|
||
+
|
||
+ private void testRemovePermissionByName(final String name) {
|
||
+ final Permission perm = new Permission(name);
|
||
+ pm.addPermission(perm);
|
||
+ assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm));
|
||
+ pm.removePermission(name);
|
||
+ assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue()));
|
||
+ }
|
||
+
|
||
+ private void testRemovePermissionByPermission(final String name) {
|
||
+ final Permission perm = new Permission(name);
|
||
+ pm.addPermission(perm);
|
||
+ assertThat("Permission \"" + name + "\" was not added", pm.getPermission(name), is(perm));
|
||
+ pm.removePermission(perm);
|
||
+ assertThat("Permission \"" + name + "\" was not removed", pm.getPermission(name), is(nullValue()));
|
||
+ }
|
||
+
|
||
+ @After
|
||
+ public void tearDown() {
|
||
+ pm.clearPlugins();
|
||
+ assertThat(pm.getPermissions(), is(empty()));
|
||
+ }
|
||
+}
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/SyntheticEventTest.java b/src/test/java/io/papermc/paper/plugin/SyntheticEventTest.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..51c08740edffd152c8e2b6d3676ff7f1ce6090c6
|
||
--- /dev/null
|
||
+++ b/src/test/java/io/papermc/paper/plugin/SyntheticEventTest.java
|
||
@@ -0,0 +1,42 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+import io.papermc.paper.plugin.manager.PaperPluginManagerImpl;
|
||
+import org.bukkit.Bukkit;
|
||
+import org.bukkit.event.Event;
|
||
+import org.bukkit.event.EventHandler;
|
||
+import org.bukkit.event.Listener;
|
||
+import org.junit.Assert;
|
||
+import org.junit.Test;
|
||
+
|
||
+public class SyntheticEventTest {
|
||
+
|
||
+ @Test
|
||
+ public void test() {
|
||
+ PaperTestPlugin paperTestPlugin = new PaperTestPlugin("synthetictest");
|
||
+ PaperPluginManagerImpl paperPluginManager = new PaperPluginManagerImpl(Bukkit.getServer(), null, null);
|
||
+
|
||
+ TestEvent event = new TestEvent(false);
|
||
+ Impl impl = new Impl();
|
||
+
|
||
+ paperPluginManager.registerEvents(impl, paperTestPlugin);
|
||
+ paperPluginManager.callEvent(event);
|
||
+
|
||
+ Assert.assertEquals(1, impl.callCount);
|
||
+ }
|
||
+
|
||
+ public abstract static class Base<E extends Event> implements Listener {
|
||
+ int callCount = 0;
|
||
+
|
||
+ public void accept(E evt) {
|
||
+ callCount++;
|
||
+ }
|
||
+ }
|
||
+
|
||
+ public static class Impl extends Base<TestEvent> {
|
||
+ @Override
|
||
+ @EventHandler
|
||
+ public void accept(TestEvent evt) {
|
||
+ super.accept(evt);
|
||
+ }
|
||
+ }
|
||
+}
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/TestEvent.java b/src/test/java/io/papermc/paper/plugin/TestEvent.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..04903794a8ee4dd73162ae240862ff6dc4cb4e24
|
||
--- /dev/null
|
||
+++ b/src/test/java/io/papermc/paper/plugin/TestEvent.java
|
||
@@ -0,0 +1,22 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+
|
||
+import org.bukkit.event.Event;
|
||
+import org.bukkit.event.HandlerList;
|
||
+
|
||
+public class TestEvent extends Event {
|
||
+ private static final HandlerList handlers = new HandlerList();
|
||
+
|
||
+ public TestEvent(boolean async) {
|
||
+ super(async);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public HandlerList getHandlers() {
|
||
+ return handlers;
|
||
+ }
|
||
+
|
||
+ public static HandlerList getHandlerList() {
|
||
+ return handlers;
|
||
+ }
|
||
+}
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java b/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..8837b131dedc41e7d0d58ccf3d1e251168c5b9b8
|
||
--- /dev/null
|
||
+++ b/src/test/java/io/papermc/paper/plugin/TestJavaPluginProvider.java
|
||
@@ -0,0 +1,42 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+import io.papermc.paper.plugin.provider.PluginProvider;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+
|
||
+import java.nio.file.Path;
|
||
+import java.util.jar.JarFile;
|
||
+import java.util.logging.Logger;
|
||
+
|
||
+public class TestJavaPluginProvider implements PluginProvider<PaperTestPlugin> {
|
||
+
|
||
+ private final TestPluginMeta testPluginConfiguration;
|
||
+
|
||
+ public TestJavaPluginProvider(TestPluginMeta testPluginConfiguration) {
|
||
+ this.testPluginConfiguration = testPluginConfiguration;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull Path getSource() {
|
||
+ return Path.of("dummy");
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public JarFile file() {
|
||
+ throw new UnsupportedOperationException();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public PaperTestPlugin createInstance() {
|
||
+ return new PaperTestPlugin(this.testPluginConfiguration);
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public TestPluginMeta getMeta() {
|
||
+ return this.testPluginConfiguration;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public Logger getLogger() {
|
||
+ return Logger.getLogger("TestPlugin");
|
||
+ }
|
||
+}
|
||
diff --git a/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java
|
||
new file mode 100644
|
||
index 0000000000000000000000000000000000000000..ba271c35eb2804f94cfc893bf94affb9ae13d3ba
|
||
--- /dev/null
|
||
+++ b/src/test/java/io/papermc/paper/plugin/TestPluginMeta.java
|
||
@@ -0,0 +1,114 @@
|
||
+package io.papermc.paper.plugin;
|
||
+
|
||
+import io.papermc.paper.plugin.configuration.PluginMeta;
|
||
+import org.bukkit.permissions.Permission;
|
||
+import org.bukkit.permissions.PermissionDefault;
|
||
+import org.bukkit.plugin.PluginLoadOrder;
|
||
+import org.jetbrains.annotations.NotNull;
|
||
+import org.jetbrains.annotations.Nullable;
|
||
+
|
||
+import java.util.List;
|
||
+
|
||
+public class TestPluginMeta implements PluginMeta {
|
||
+
|
||
+ private final String identifier;
|
||
+ private List<String> hardDependencies = List.of();
|
||
+ private List<String> softDependencies = List.of();
|
||
+ private List<String> loadBefore = List.of();
|
||
+
|
||
+ public TestPluginMeta(String identifier) {
|
||
+ this.identifier = identifier;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getName() {
|
||
+ return this.identifier;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getMainClass() {
|
||
+ return "null";
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull PluginLoadOrder getLoadOrder() {
|
||
+ return PluginLoadOrder.POSTWORLD;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getVersion() {
|
||
+ return "1.0";
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable String getLoggerPrefix() {
|
||
+ return this.identifier;
|
||
+ }
|
||
+
|
||
+ public void setHardDependencies(List<String> hardDependencies) {
|
||
+ this.hardDependencies = hardDependencies;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getPluginDependencies() {
|
||
+ return this.hardDependencies;
|
||
+ }
|
||
+
|
||
+ public void setSoftDependencies(List<String> softDependencies) {
|
||
+ this.softDependencies = softDependencies;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getPluginSoftDependencies() {
|
||
+ return this.softDependencies;
|
||
+ }
|
||
+
|
||
+ public void setLoadBefore(List<String> loadBefore) {
|
||
+ this.loadBefore = loadBefore;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getLoadBeforePlugins() {
|
||
+ return this.loadBefore;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getProvidedPlugins() {
|
||
+ return List.of();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getAuthors() {
|
||
+ return List.of();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<String> getContributors() {
|
||
+ return List.of();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable String getDescription() {
|
||
+ return "null";
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @Nullable String getWebsite() {
|
||
+ return "null";
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull List<Permission> getPermissions() {
|
||
+ return List.of();
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull PermissionDefault getPermissionDefault() {
|
||
+ return PermissionDefault.TRUE;
|
||
+ }
|
||
+
|
||
+ @Override
|
||
+ public @NotNull String getAPIVersion() {
|
||
+ return "null";
|
||
+ }
|
||
+}
|
||
diff --git a/src/test/java/io/papermc/paper/testing/DummyServer.java b/src/test/java/io/papermc/paper/testing/DummyServer.java
|
||
index 286790061c9d81c872108ef63e1a1aba2fbec809..53ac8df47a2c9a84c751955fd4fbb352a3ded16b 100644
|
||
--- a/src/test/java/io/papermc/paper/testing/DummyServer.java
|
||
+++ b/src/test/java/io/papermc/paper/testing/DummyServer.java
|
||
@@ -57,7 +57,7 @@ public final class DummyServer {
|
||
return new LazyRegistry(() -> CraftRegistry.createRegistry(invocation.getArgument(0, Class.class), AbstractTestingBase.REGISTRY_CUSTOM));
|
||
});
|
||
|
||
- final PluginManager pluginManager = new SimplePluginManager(dummyServer, new SimpleCommandMap(dummyServer));
|
||
+ final PluginManager pluginManager = new io.papermc.paper.plugin.manager.PaperPluginManagerImpl(dummyServer, new SimpleCommandMap(dummyServer), null);
|
||
when(dummyServer.getPluginManager()).thenReturn(pluginManager);
|
||
|
||
Bukkit.setServer(dummyServer);
|