ScalaLoader icon

ScalaLoader -----

Write plugins in Scala!



So you want to write plugins in Scala? Great! Or not so great? Scala runtime classes are not on the class/module path by default. Server administrators could add them using the -classpath commandline option, but in reality most bukkit servers run in managed environments by minecraft-specialized server hosts. The standard workaround for this problem is to include the classes in your own plugin and relocate them using a shading plugin in your build process. While this does work, it means that Scala library classes are no longer shared between plugins, making them incompatible because the scala classes will be loaded by different classloaders. Since Spigot 1.16.5 there is the option to include the dependency in the plugin.yml, but this approach has the same problems. And there is the problem of unnecessary ram and disc usage too. As of Scala 2.12.6, the standard library has a size of 3.5 MB. The reflection library is another 5 MB. Using both libraries in multiple plugins results unnecessarily large plugins sizes, while really those Scala classes should only be loaded once. Introducing…

ScalaLoader

ScalaLoader uses a custom PluginLoader that loads the Scala runtime classes for you! You can use this Giter8 template to get started quickly!

Pros
  • Write idiomatic Scala!
  • Able to use a true singleton for your plugin's main class, through Scala's object!
  • Supports multiple Scala versions at the same time! ScalaLoader uses classloader magic to make that work.
  • Supports custom scala version by adding/changing URLs in the config file.
  • Annotation-based detection of the plugin's main class - no need to write a plugin.yml. If you wish to use a plugin.yml still, you can, however I always found it a pain.
  • A boilerplate-free event api! No need to implement #getHandlerList(), #getHandlers(), #isCancelled() and #setCancelled(boolean)!
  • A boilerplate-free ConfigurationSerializable api! No need to implement #serialize() and #deserialize(Map<String,Object>)! (Scala collection support is a work in progress)

Cons
  • Scala library classes are only accessible to ScalaPlugins (You can still write them in Java though).
  • ScalaLoader uses a lot of reflection/injection hacks to make ScalaPlugins accessible to JavaPlugins. This may not work correctly if there is a SecurityManager in place.

Caveats

  • ScalaPlugin jars go in the <server_root>/plugins/ScalaLoader/scalaplugins/ directory. I made this choice so that ScalaLoader doesn't try to load JavaPlugins that are loaded already.
  • By default ScalaLoader downloads the scala libraries from over the network the first time. I made this choice to provide the best possible user experience for server admins. If you're very security-focused you might want to provide your own jars by changing the URLs to "file://some/location.jar". The scala classes aren't actually loaded until there's a plugin that needs them, so you can run ScalaLoader once without ScalaPlugins to generate the config.

I know what I'm doing, just give me what I need!
Maven:
Code (XML):

<repositories>
    <repository>
        <id>jannyboy11-minecraft-repo </id>
        <url>https://repo.repsy.io/mvn/jannyboy11/minecraft/ </url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.janboerman.scalaloader </groupId>
        <artifactId>ScalaLoader </artifactId>
        <version>0.18.15-SNAPSHOT </version>
        <scope>provided </scope>
    </dependency>
</dependencies>
 
SBT:
Code (Scala):

resolvers + = "jannyboy11-minecraft-repo" at "https://repo.repsy.io/mvn/jannyboy11/minecraft"
libraryDependencies + = "com.janboerman.scalaloader" % "ScalaLoader" % "0.18.15-SNAPSHOT" % "provided"
 

Javadocs
Available on GitHub Pages.

Example plugin
Code (Scala):
package xyz. janboerman. scalaloader. example. scala

import org. bukkit. ChatColor
import org. bukkit. command. {CommandSender, Command }
import org. bukkit. event. {EventHandler, Listener }
import org. bukkit. event. player. PlayerJoinEvent
import org. bukkit. permissions. PermissionDefault
import xyz. janboerman. scalaloader. plugin. ScalaPluginDescription. {Command => SPCommand, Permission => SPPermission }
import xyz. janboerman. scalaloader. plugin. {ScalaPlugin, ScalaPluginDescription }
import xyz. janboerman. scalaloader. plugin. description. {Scala, ScalaVersion, Api, ApiVersion }

@Scala (version = ScalaVersion. v2_13_10 )
@Api (ApiVersion. v1_19 )
object ExamplePlugin
    extends ScalaPlugin ( new ScalaPluginDescription ( "ScalaExample", "0.1-SNAPSHOT" )
        . commands ( new SPCommand ( "foo" )
            . permission ( "scalaexample.foo" ) )
        . permissions ( new SPPermission ( "scalaexample.foo" )
            . permissionDefault (PermissionDefault. TRUE ) ) )
    with Listener {

    getLogger ( ). info ( "ScalaExample - I am constructed!" )

    override def onLoad ( ) : Unit = {
        getLogger. info ( "ScalaExample - I am loaded!" )
    }

    override def onEnable ( ) : Unit = {
        getLogger. info ( "ScalaExample - I am enabled!" )
        getServer. getPluginManager. registerEvents ( this, this )
    }

    override def onDisable ( ) : Unit = {
        getLogger. info ( "ScalaExample - I am disabled!" )
    }

    @EventHandler
    def onJoin (event : PlayerJoinEvent ) : Unit = {
        event. setJoinMessage (s "${ChatColor.GREEN}Howdy ${event.getPlayer.getName}!" )
    }

    override def onCommand (sender : CommandSender, command : Command, label : String, args : Array [String ] ) : Boolean = {
        sender. sendMessage ( "Executed foo command!" )
        true
    }

    def getInt ( ) = 42

}
Depending on a ScalaPlugin from a JavaPlugin
plugin.yml:
Code (YAML):
name : DummyPlugin
version
: 1.0
main
: xyz.janboerman.dummy.dummyplugin.DummyPlugin
depend
: [ScalaLoader ]
softdepend
: [ScalaExample ] #A hard dependency will not work! Your plugin will not load!
Java code:
Code (Java):
package xyz.janboerman.dummy.dummyplugin ;

import org.bukkit.plugin.java.JavaPlugin ;
import xyz.janboerman.scalaloader.compat.IScalaPluginLoader ;
import xyz. janboerman. scalaloader. example. scala. ExamplePlugin$ ;

public final class DummyPlugin extends JavaPlugin {

    @Override
    public void onEnable ( ) {
        //get the plugin instance
        ExamplePlugin$ plugin = (ExamplePlugin$ ) getServer ( ). getPluginManager ( ). getPlugin ( "ScalaExample" ) ;

        //make sure all classes from the scala plugin can be accessed
        IScalaPluginLoader. openUpToJavaPlugin (plugin, this ) ;

        //do whatever you want afterwards!
        getLogger ( ). info ( "We got " + plugin. getInt ( ) + " from Scala!" ) ;
    }

}

Commands & Permissions
Since this plugin is meant for other developers, the commands and permissions are probably not very useful to end-users. But for completeness here they are:
  • /resetScalaUrls <scala version>: Resets the URLs in the config for a version of Scala. Useful if you overwrote some of them. If 'all' is provided as a scala version then all URLs will be reset. Permission: scalaloader.resetscalaurls
  • /listScalaPlugins: Outputs the names of all scala plugins. Binary-compatible plugins are shown on the same line. Permission: scalaloader.listscalaplugins
  • /dumpClass <plugin> <class file> <format>?: Outpus the bytecode of a class to the console in a textual representation. This command is mostly useful for myself, but if you fancy diving into Java bytecode then more power to you! Permission: scalaloader.dumpclass
  • /setDebug <class name>: Marks or unmarks a class as a class that should be dumped to the console by the classloader when it is loaded. A list is saved in the debug.yml file. Permission: scalaloader.setdebug
  • /classMembers <class name>: Dumps fields and methods visible to ScalaLoader to the command sender. Permission: scalaloader.classmembers
All permissions are by default only granted to server operators.

Usage statistics
ScalaLoader collects some anonymous metrics and sends that to bStats.org. In addition to the standard charts, you can see here which Scala versions are most in use. Opting out can be done by editing the <server_root>/plugins/bStats/config.yml file.

Chat & Support

GitHub issues is the place for bug reports and feature requests, but if you just want to chat about the project, or want to know more about how to use it, you can reach me via Discord: https://discord.gg/pb2cBBKdwq
Resource Information
Author:
----------
Total Downloads: 3,050
First Release: Aug 7, 2018
Last Update: Dec 26, 2024
Category: ---------------
All-Time Rating:
3 ratings
Version -----
Released: --------------------
Downloads: ------
Version Rating:
----------------------
-- ratings