diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java index aa44d4685d..9c79d8757f 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java @@ -2,12 +2,13 @@ package io.papermc.paper.command.subcommands; import com.destroystokyo.paper.util.SneakyThrow; import io.papermc.paper.command.PaperSubcommand; -import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; @@ -16,6 +17,7 @@ import java.util.List; import java.util.Locale; import java.util.Set; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; import net.minecraft.server.MinecraftServer; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -23,6 +25,7 @@ import org.bukkit.event.HandlerList; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.RegisteredListener; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.framework.qual.DefaultQualifier; import static net.kyori.adventure.text.Component.newline; @@ -35,6 +38,8 @@ import static net.kyori.adventure.text.format.NamedTextColor.WHITE; @DefaultQualifier(NonNull.class) public final class DumpListenersCommand implements PaperSubcommand { + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); + private static final String COMMAND_ARGUMENT_TO_FILE = "tofile"; private static final MethodHandle EVENT_TYPES_HANDLE; static { @@ -49,7 +54,7 @@ public final class DumpListenersCommand implements PaperSubcommand { @Override public boolean execute(final CommandSender sender, final String subCommand, final String[] args) { - if (args.length >= 1 && args[0].equals("tofile")) { + if (args.length >= 1 && args[0].equals(COMMAND_ARGUMENT_TO_FILE)) { this.dumpToFile(sender); return true; } @@ -58,45 +63,69 @@ public final class DumpListenersCommand implements PaperSubcommand { } private void dumpToFile(final CommandSender sender) { - final File file = new File("debug/listeners-" - + DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss").format(LocalDateTime.now()) + ".txt"); - file.getParentFile().mkdirs(); - try (final PrintWriter writer = new PrintWriter(file)) { - for (final String eventClass : eventClassNames()) { - final HandlerList handlers; - try { - handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null); - } catch (final ReflectiveOperationException e) { - continue; - } - if (handlers.getRegisteredListeners().length != 0) { - writer.println(eventClass); - } - for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) { - writer.println(" - " + registeredListener); + Path parent = Path.of("debug"); + Path path = parent.resolve("listeners-" + FORMATTER.format(LocalDateTime.now()) + ".txt"); + sender.sendMessage( + text("Writing listeners into directory", GREEN) + .appendSpace() + .append( + text(parent.toString(), WHITE) + .hoverEvent(text("Click to copy the full path of debug directory", WHITE)) + .clickEvent(ClickEvent.copyToClipboard(parent.toAbsolutePath().toString())) + ) + ); + try { + Files.createDirectories(parent); + Files.createFile(path); + try (final PrintWriter writer = new PrintWriter(path.toFile())){ + for (final String eventClass : eventClassNames()) { + final HandlerList handlers; + try { + handlers = (HandlerList) findClass(eventClass).getMethod("getHandlerList").invoke(null); + } catch (final ReflectiveOperationException e) { + continue; + } + if (handlers.getRegisteredListeners().length != 0) { + writer.println(eventClass); + } + for (final RegisteredListener registeredListener : handlers.getRegisteredListeners()) { + writer.println(" - " + registeredListener); + } } } } catch (final IOException ex) { - throw new RuntimeException(ex); + sender.sendMessage(text("Failed to write dumped listener! See the console for more info.", RED)); + MinecraftServer.LOGGER.warn("Error occurred while dumping listeners", ex); + return; } - sender.sendMessage(text("Dumped listeners to " + file, GREEN)); + sender.sendMessage( + text("Successfully written listeners into", GREEN) + .appendSpace() + .append( + text(path.toString(), WHITE) + .hoverEvent(text("Click to copy the full path of the file", WHITE)) + .clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString())) + ) + ); } private void doDumpListeners(final CommandSender sender, final String[] args) { if (args.length == 0) { - sender.sendMessage(text("Usage: /paper dumplisteners tofile|", RED)); + sender.sendMessage(text("Usage: /paper dumplisteners " + COMMAND_ARGUMENT_TO_FILE + "|", RED)); return; } + final String className = args[0]; + try { - final HandlerList handlers = (HandlerList) findClass(args[0]).getMethod("getHandlerList").invoke(null); + final HandlerList handlers = (HandlerList) findClass(className).getMethod("getHandlerList").invoke(null); if (handlers.getRegisteredListeners().length == 0) { - sender.sendMessage(text(args[0] + " does not have any registered listeners.")); + sender.sendMessage(text(className + " does not have any registered listeners.")); return; } - sender.sendMessage(text("Listeners for " + args[0] + ":")); + sender.sendMessage(text("Listeners for " + className + ":")); for (final RegisteredListener listener : handlers.getRegisteredListeners()) { final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", WHITE) @@ -115,12 +144,12 @@ public final class DumpListenersCommand implements PaperSubcommand { sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length)); } catch (final ClassNotFoundException e) { - sender.sendMessage(text("Unable to find a class named '" + args[0] + "'. Make sure to use the fully qualified name.", RED)); + sender.sendMessage(text("Unable to find a class named '" + className + "'. Make sure to use the fully qualified name.", RED)); } catch (final NoSuchMethodException e) { - sender.sendMessage(text("Class '" + args[0] + "' does not have a valid getHandlerList method.", RED)); + sender.sendMessage(text("Class '" + className + "' does not have a valid getHandlerList method.", RED)); } catch (final ReflectiveOperationException e) { sender.sendMessage(text("Something went wrong, see the console for more details.", RED)); - MinecraftServer.LOGGER.warn("Error occurred while dumping listeners for class " + args[0], e); + MinecraftServer.LOGGER.warn("Error occurred while dumping listeners for class {}", className, e); } } @@ -137,7 +166,7 @@ public final class DumpListenersCommand implements PaperSubcommand { private static List suggestions() { final List ret = new ArrayList<>(); - ret.add("tofile"); + ret.add(COMMAND_ARGUMENT_TO_FILE); ret.addAll(eventClassNames()); return ret; } diff --git a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java index d4a092243e..0f8983d07e 100644 --- a/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java +++ b/paper-server/src/main/java/io/papermc/paper/command/subcommands/DumpPluginsCommand.java @@ -5,6 +5,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import com.google.gson.Strictness; import com.google.gson.internal.Streams; import com.google.gson.stream.JsonWriter; import io.papermc.paper.command.PaperSubcommand; @@ -15,7 +16,6 @@ import io.papermc.paper.plugin.entrypoint.classloader.group.PaperPluginClassLoad import io.papermc.paper.plugin.entrypoint.classloader.group.SimpleListPluginClassLoaderGroup; import io.papermc.paper.plugin.entrypoint.classloader.group.SpigotPluginClassLoaderGroup; import io.papermc.paper.plugin.entrypoint.classloader.group.StaticPluginClassLoaderGroup; -import io.papermc.paper.plugin.entrypoint.dependency.GraphDependencyContext; import io.papermc.paper.plugin.entrypoint.dependency.SimpleMetaDependencyTree; import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; @@ -27,12 +27,14 @@ 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.kyori.adventure.text.event.ClickEvent; 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.IOException; import java.io.PrintStream; import java.io.StringWriter; import java.nio.charset.StandardCharsets; @@ -47,6 +49,7 @@ 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; +import static net.kyori.adventure.text.format.NamedTextColor.WHITE; @DefaultQualifier(NonNull.class) public final class DumpPluginsCommand implements PaperSubcommand { @@ -60,24 +63,40 @@ public final class DumpPluginsCommand implements PaperSubcommand { 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"); + Path path = parent.resolve("plugin-info-" + FORMATTER.format(LocalDateTime.now()) + ".json"); try { Files.createDirectories(parent); Files.createFile(path); - sender.sendMessage(text("Writing plugin information to " + path, GREEN)); + sender.sendMessage( + text("Writing plugin information into directory", GREEN) + .appendSpace() + .append( + text(parent.toString(), WHITE) + .hoverEvent(text("Click to copy the full path of debug directory", WHITE)) + .clickEvent(ClickEvent.copyToClipboard(parent.toAbsolutePath().toString())) + ) + ); final JsonObject data = this.writeDebug(); StringWriter stringWriter = new StringWriter(); JsonWriter jsonWriter = new JsonWriter(stringWriter); jsonWriter.setIndent(" "); - jsonWriter.setLenient(false); + jsonWriter.setStrictness(Strictness.STRICT); 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)); + sender.sendMessage( + text("Successfully written plugin debug information into", GREEN) + .appendSpace() + .append( + text(path.toString(), WHITE) + .hoverEvent(text("Click to copy the full path of the file", WHITE)) + .clickEvent(ClickEvent.copyToClipboard(path.toAbsolutePath().toString())) + ) + ); } 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); @@ -97,6 +116,7 @@ public final class DumpPluginsCommand implements PaperSubcommand { return root; } + @SuppressWarnings("unchecked") private void writeProviders(JsonObject root) { JsonObject rootProviders = new JsonObject(); root.add("providers", rootProviders); @@ -116,7 +136,6 @@ public final class DumpPluginsCommand implements PaperSubcommand { providerObj.addProperty("soft-dependencies", provider.getMeta().getPluginSoftDependencies().toString()); providerObj.addProperty("load-before", provider.getMeta().getLoadBeforePlugins().toString()); - providers.add(providerObj); pluginProviders.add((PluginProvider) provider); }