Solved Adding event listener from groovy

Discussion in 'Plugin Development' started by regic, Apr 9, 2022.

  1. Offline

    regic

    Hello,

    I'm trying to add a simple login event listener from groovy, but it gives the error:

    Code:
    java.lang.IllegalArgumentException: class LoginListener is not provided by class org.bukkit.plugin.java.PluginClassLoader
        at org.bukkit.plugin.java.JavaPlugin.getProvidingPlugin(JavaPlugin.java:428) ~[purpur-api-1.18.1-R0.1-SNAPSHOT.jar:?]
        at org.bukkit.plugin.EventExecutor.create(EventExecutor.java:55) ~[purpur-api-1.18.1-R0.1-SNAPSHOT.jar:?]
        at org.bukkit.plugin.java.JavaPluginLoader.createRegisteredListeners(JavaPluginLoader.java:336) ~[purpur-api-1.18.1-R0.1-SNAPSHOT.jar:?]
        at org.bukkit.plugin.SimplePluginManager.registerEvents(SimplePluginManager.java:665) ~[purpur-api-1.18.1-R0.1-SNAPSHOT.jar:?]
        at org.bukkit.plugin.PluginManager$registerEvents.call(Unknown Source) ~[?:?]
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47) ~[bukkit-groovy-1-jar-with-dependencies.jar:?]
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) ~[bukkit-groovy-1-jar-with-dependencies.jar:?]
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:148) ~[bukkit-groovy-1-jar-with-dependencies.jar:?]
        at OnLogin.onLoad(on_login.groovy:34) ~[?:?]
    
    groovy script attached. OnLogin's onLoad is called when the plugin is loaded:
    Code:
                LoadableScript script = (LoadableScript) groovyScriptEngine.run(fullName, binding);
                scripts.put(fullName, script);
                script.onLoad(javaPlugin);
                Bukkit.getLogger().info("Script loaded: " + fullName);
    Maybe java reflection doesn't work with groovy classes the way bukkit expects it to?

    One possible problem: groovy cannot export 2 classes from the same script, see https://issues.apache.org/jira/browse/GROOVY-3793

    Edit: even if I export only one class I get the same error

    I've found the problem. This is probably way too specific to be useful to anyone, but who knows...

    TLDR
    always set the return value of event handler functions to void. It won't always cause a problem if you don't but a warning explicitly says it is unsupported and will stop working in the future.

    Long version
    The problem is that groovy did not define the return value of the onLogin function as void, which meant the
    EventExecutor.create function tries to print a warning which includes the plugin's name. The problem is that it tries to find the plugin's name through the classloader (!) of the event handler class (see line 55 in the EventExecutor.java in purpur version 1.18.2):
    Code:
               final org.bukkit.plugin.java.JavaPlugin plugin = org.bukkit.plugin.java.JavaPlugin.getProvidingPlugin(m.getDeclaringClass());
               org.bukkit.Bukkit.getLogger().warning("@EventHandler method " + m.getDeclaringClass().getName() + (Modifier.isStatic(m.getModifiers()) ? '.' : '#') + m.getName()
                    + " returns non-void type " + m.getReturnType().getName() + ". This is unsupported behavior and will no longer work in a future version of Paper."
                    + " This should be reported to the developers of " + plugin.getDescription().getFullName() + " (" + String.join(",", plugin.getDescription().getAuthors()) + ')');
    Since the class was created in groovy, its classloader is GroovyClassLoader not JavaPluginClassLoader (which is normally used to load the classes inside a plugin). See line 427 in JavaPlugin.java (purpur 1.18.2):
    Code:
            final ClassLoader cl = clazz.getClassLoader();
            if (!(cl instanceof PluginClassLoader)) {
                throw new IllegalArgumentException(clazz + " is not provided by " + PluginClassLoader.class);
            }
    
    If I explicitly set the return value to void in the groovy script, the problem goes away. I don't know if this behavior is present in other paper forks as well, but I guess it is.
     

    Attached Files:

    Last edited by a moderator: Apr 10, 2022
  2. Offline

    Shqep

    Why not try to let the class in the script extend JavaPlugin too, and register the listener using that same class? I've done this before to load a plugin in pure Groovy, and idk why I feel like this may not work now for some reasons.

    I mean, void or not, it still calls the method without caring about the returned result, so that shouldn't be a big problem. But still, it's better to put it as void, cuz you never know if the Spigot/Paper dudes decide to use that returned result in the future, ig.
     
  3. Offline

    regic

    The return value only mattered because it triggered a warning (otherwise paper doesn't care about it) and one information included in the warning's text (the plugin's name) is not available if the classloader is not the JavaPluginClassLoader.

    You are right: the warning explicitly says the return value should be void, but to be honest I'm just inexperienced in groovy and thought it will infer the return type from function body.

    Writing the whole plugin in groovy is an interesting idea, but for now this will suffice. The java part is a fairly minimal script loader anyway. The goal is to be able to reload scripts on the fly without reloading the whole plugin so I don't want to pre-compile them.
     
    Last edited: Apr 10, 2022

Share This Page