From f1583fcd7414bde1ee2efc2b1395a82cdb78b8a2 Mon Sep 17 00:00:00 2001
From: Jason <11360596+jpenilla@users.noreply.github.com>
Date: Sat, 26 Nov 2022 10:36:11 -0700
Subject: [PATCH] Add `/paper dumplisteners tofile` and increase detail of
 command output (#8592)

---
 ...0409-Add-paper-dumplisteners-command.patch | 140 ++++++++++++++++++
 ...0943-Add-paper-dumplisteners-command.patch |  75 ++++++++--
 2 files changed, 203 insertions(+), 12 deletions(-)

diff --git a/patches/api/0409-Add-paper-dumplisteners-command.patch b/patches/api/0409-Add-paper-dumplisteners-command.patch
index 3dd64e3012..e2d28d0fa2 100644
--- a/patches/api/0409-Add-paper-dumplisteners-command.patch
+++ b/patches/api/0409-Add-paper-dumplisteners-command.patch
@@ -4,6 +4,88 @@ Date: Sat, 19 Nov 2022 19:46:44 +0100
 Subject: [PATCH] Add /paper dumplisteners command
 
 
+diff --git a/src/main/java/co/aikar/timings/TimedEventExecutor.java b/src/main/java/co/aikar/timings/TimedEventExecutor.java
+index 4e6e1b8e8aeb07e34536941d2cbfc25e5cfa6c27..34e43e56ccc663e05b9cae36643e8df5eee5cb17 100644
+--- a/src/main/java/co/aikar/timings/TimedEventExecutor.java
++++ b/src/main/java/co/aikar/timings/TimedEventExecutor.java
+@@ -80,4 +80,10 @@ public class TimedEventExecutor implements EventExecutor {
+             executor.execute(listener, event);
+         }
+     }
++
++    @Override
++    @NotNull
++    public String toString() {
++        return "TimedEventExecutor['" + this.executor.toString() + "']";
++    }
+ }
+diff --git a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
+index 5b28e9b1daba7834af67dbc193dd656bedd9a994..fbebf649e893cf872be9b27091146a7c2f451aca 100644
+--- a/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
++++ b/src/main/java/com/destroystokyo/paper/event/executor/MethodHandleEventExecutor.java
+@@ -14,10 +14,12 @@ import org.jetbrains.annotations.NotNull;
+ public class MethodHandleEventExecutor implements EventExecutor {
+     private final Class<? extends Event> eventClass;
+     private final MethodHandle handle;
++    private final Method method;
+ 
+     public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull MethodHandle handle) {
+         this.eventClass = eventClass;
+         this.handle = handle;
++        this.method = null;
+     }
+ 
+     public MethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull Method m) {
+@@ -28,6 +30,7 @@ public class MethodHandleEventExecutor implements EventExecutor {
+         } catch (IllegalAccessException e) {
+             throw new AssertionError("Unable to set accessible", e);
+         }
++        this.method = m;
+     }
+ 
+     @Override
+@@ -39,4 +42,10 @@ public class MethodHandleEventExecutor implements EventExecutor {
+             SneakyThrow.sneaky(t);
+         }
+     }
++
++    @Override
++    @NotNull
++    public String toString() {
++        return "MethodHandleEventExecutor['" + this.method + "']";
++    }
+ }
+diff --git a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
+index c83672427324bd068ed52916f700b68446a226f6..87ea5354808dbbdefbdfc78f352a543f72a0d033 100644
+--- a/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
++++ b/src/main/java/com/destroystokyo/paper/event/executor/StaticMethodHandleEventExecutor.java
+@@ -18,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
+ public class StaticMethodHandleEventExecutor implements EventExecutor {
+     private final Class<? extends Event> eventClass;
+     private final MethodHandle handle;
++    private final Method method;
+ 
+     public StaticMethodHandleEventExecutor(@NotNull Class<? extends Event> eventClass, @NotNull Method m) {
+         Preconditions.checkArgument(Modifier.isStatic(m.getModifiers()), "Not a static method: %s", m);
+@@ -29,6 +30,7 @@ public class StaticMethodHandleEventExecutor implements EventExecutor {
+         } catch (IllegalAccessException e) {
+             throw new AssertionError("Unable to set accessible", e);
+         }
++        this.method = m;
+     }
+ 
+     @Override
+@@ -40,4 +42,10 @@ public class StaticMethodHandleEventExecutor implements EventExecutor {
+             SneakyThrow.sneaky(throwable);
+         }
+     }
++
++    @Override
++    @NotNull
++    public String toString() {
++        return "StaticMethodHandleEventExecutor['" + this.method + "']";
++    }
+ }
 diff --git a/src/main/java/org/bukkit/event/HandlerList.java b/src/main/java/org/bukkit/event/HandlerList.java
 index ed78cca71f83b296d082d0af147ca8d622c7606a..2292bd460ce2be113beb4ba6b4eb19350060f01c 100644
 --- a/src/main/java/org/bukkit/event/HandlerList.java
@@ -35,3 +117,61 @@ index ed78cca71f83b296d082d0af147ca8d622c7606a..2292bd460ce2be113beb4ba6b4eb1935
          handlerslots = new EnumMap<EventPriority, ArrayList<RegisteredListener>>(EventPriority.class);
          for (EventPriority o : EventPriority.values()) {
              handlerslots.put(o, new ArrayList<RegisteredListener>());
+diff --git a/src/main/java/org/bukkit/plugin/EventExecutor.java b/src/main/java/org/bukkit/plugin/EventExecutor.java
+index e1860322ae0f3c35097d16767628744034941749..bdfa3b5f4ab5023d3e1b5c50bed885b6aa118a02 100644
+--- a/src/main/java/org/bukkit/plugin/EventExecutor.java
++++ b/src/main/java/org/bukkit/plugin/EventExecutor.java
+@@ -70,9 +70,18 @@ public interface EventExecutor {
+             try {
+                 EventExecutor asmExecutor = executorClass.newInstance();
+                 // Define a wrapper to conform to bukkit stupidity (passing in events that don't match and wrapper exception)
+-                return (listener, event) -> {
+-                    if (!eventClass.isInstance(event)) return;
+-                    asmExecutor.execute(listener, event);
++                return new EventExecutor() {
++                    @Override
++                    public void execute(@NotNull Listener listener, @NotNull Event event) throws EventException {
++                        if (!eventClass.isInstance(event)) return;
++                        asmExecutor.execute(listener, event);
++                    }
++
++                    @Override
++                    @NotNull
++                    public String toString() {
++                        return "ASMEventExecutor['" + m + "']";
++                    }
+                 };
+             } catch (InstantiationException | IllegalAccessException e) {
+                 throw new AssertionError("Unable to initialize generated event executor", e);
+diff --git a/src/main/java/org/bukkit/plugin/RegisteredListener.java b/src/main/java/org/bukkit/plugin/RegisteredListener.java
+index 419aec56b0e3fa8bcec2ea7f340caa3456b57d00..3b3d9642a8d63798dc28f2f8df77f0466451cbff 100644
+--- a/src/main/java/org/bukkit/plugin/RegisteredListener.java
++++ b/src/main/java/org/bukkit/plugin/RegisteredListener.java
+@@ -78,4 +78,27 @@ public class RegisteredListener {
+     public boolean isIgnoringCancelled() {
+         return ignoreCancelled;
+     }
++
++    // Paper start
++    /**
++     * Get the executor for this registration.
++     *
++     * @return executor
++     */
++    @NotNull
++    public EventExecutor getExecutor() {
++        return this.executor;
++    }
++
++    @Override
++    public String toString() {
++        return "RegisteredListener{"
++            + "plugin=\"" + this.plugin.getName()
++            + "\", listener=\"" + this.listener
++            + "\", executor=\"" + this.executor
++            + "\", priority=\"" + this.priority.name() + " (" + this.priority.getSlot() + ")"
++            + "\", ignoringCancelled=" + this.ignoreCancelled
++            + "}";
++    }
++    // Paper end
+ }
diff --git a/patches/server/0943-Add-paper-dumplisteners-command.patch b/patches/server/0943-Add-paper-dumplisteners-command.patch
index 2d1e89e37c..a5ac06d05d 100644
--- a/patches/server/0943-Add-paper-dumplisteners-command.patch
+++ b/patches/server/0943-Add-paper-dumplisteners-command.patch
@@ -27,35 +27,44 @@ index 724592234e2a178a518f6ab7d09c3180780371a7..92154550b41b2e1d03deb1271b71bb3b
              .flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
 diff --git a/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b83fa0eeb4
+index 0000000000000000000000000000000000000000..c80485b9b0c189820687b3a8ff66a855a4b1efa7
 --- /dev/null
 +++ b/src/main/java/io/papermc/paper/command/subcommands/DumpListenersCommand.java
-@@ -0,0 +1,120 @@
+@@ -0,0 +1,171 @@
 +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.time.LocalDateTime;
++import java.time.format.DateTimeFormatter;
++import java.util.ArrayList;
 +import java.util.Collections;
 +import java.util.List;
 +import java.util.Locale;
 +import java.util.Set;
-+import java.util.stream.Stream;
++import net.kyori.adventure.text.Component;
 +import net.minecraft.server.MinecraftServer;
 +import org.bukkit.Bukkit;
 +import org.bukkit.command.CommandSender;
 +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.framework.qual.DefaultQualifier;
 +
++import static net.kyori.adventure.text.Component.newline;
 +import static net.kyori.adventure.text.Component.space;
 +import static net.kyori.adventure.text.Component.text;
 +import static net.kyori.adventure.text.format.NamedTextColor.GRAY;
 +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 DumpListenersCommand implements PaperSubcommand {
@@ -73,13 +82,41 @@ index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b8
 +
 +    @Override
 +    public boolean execute(final CommandSender sender, final String subCommand, final String[] args) {
++        if (args.length >= 1 && args[0].equals("tofile")) {
++            this.dumpToFile(sender);
++            return true;
++        }
 +        this.doDumpListeners(sender, args);
 +        return true;
 +    }
 +
++    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");
++        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);
++                }
++            }
++        } catch (final IOException ex) {
++            throw new RuntimeException(ex);
++        }
++        sender.sendMessage(text("Dumped listeners to " + file, GREEN));
++    }
++
 +    private void doDumpListeners(final CommandSender sender, final String[] args) {
 +        if (args.length == 0) {
-+            sender.sendMessage(text("Usage: /paper dumplisteners [className]", RED));
++            sender.sendMessage(text("Usage: /paper dumplisteners tofile|<className>", RED));
 +            return;
 +        }
 +
@@ -93,12 +130,19 @@ index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b8
 +
 +            sender.sendMessage(text("Listeners for " + args[0] + ":"));
 +
-+            Stream.of(handlers.getRegisteredListeners())
-+                .map(listener -> text(listener.getPlugin().getName(), GREEN)
++            for (final RegisteredListener listener : handlers.getRegisteredListeners()) {
++                final Component hoverText = text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", WHITE)
++                    .append(newline())
++                    .append(text("Listener: " + listener.getListener()))
++                    .append(newline())
++                    .append(text("Executor: " + listener.getExecutor()))
++                    .append(newline())
++                    .append(text("Ignoring cancelled: " + listener.isIgnoringCancelled()));
++
++                sender.sendMessage(text(listener.getPlugin().getName(), GREEN)
 +                    .append(space())
-+                    .append(text("(" + listener.getListener().getClass().getName() + ")", GRAY)
-+                        .hoverEvent(text("Priority: " + listener.getPriority().name() + " (" + listener.getPriority().getSlot() + ")", GRAY))))
-+                .forEach(sender::sendMessage);
++                    .append(text("(" + listener.getListener().getClass().getName() + ")", GRAY).hoverEvent(hoverText)));
++            }
 +
 +            sender.sendMessage(text("Total listeners: " + handlers.getRegisteredListeners().length));
 +
@@ -115,16 +159,23 @@ index 0000000000000000000000000000000000000000..dab8d6fb489f8b3acc7c8fdaa1b5b6b8
 +    @Override
 +    public List<String> tabComplete(final CommandSender sender, final String subCommand, final String[] args) {
 +        return switch (args.length) {
-+            case 0 -> List.copyOf(getEventClasses());
-+            case 1 -> getEventClasses().stream()
++            case 0 -> suggestions();
++            case 1 -> suggestions().stream()
 +                .filter(clazz -> clazz.toLowerCase(Locale.ROOT).contains(args[0].toLowerCase(Locale.ROOT)))
 +                .toList();
 +            default -> Collections.emptyList();
 +        };
 +    }
 +
++    private static List<String> suggestions() {
++        final List<String> ret = new ArrayList<>();
++        ret.add("tofile");
++        ret.addAll(eventClassNames());
++        return ret;
++    }
++
 +    @SuppressWarnings("unchecked")
-+    private static Set<String> getEventClasses() {
++    private static Set<String> eventClassNames() {
 +        try {
 +            return (Set<String>) EVENT_TYPES_HANDLE.invokeExact();
 +        } catch (final Throwable e) {