Bukkit can hold Scala!

Scala has been a fairly interesting language to me ever since dealing with it a few years ago for a paper on functional programming; when I saw it referenced again recently (in an article about D, of all things), I decided to give it a try. Scala is notable for being runnable on the JVM, with the right libraries - and what better way could there be to learn a JVM-compatible language than to write a Bukkit plugin with it?

I based my work off an existing plugin I had written a few months ago: AntiFreeze, a five-file affair that stops the spread of ice on Bukkit servers. The size of AntiFreeze would make it easy to convert and to base future work on, and AntiFreeze already linked against several libraries I enjoyed working with when developing Bukkit plugins. The goal of the new plugin (called ScalaMC) would be simply to:

  • Respond to a simple command, /hello, with a message to the issuing user
  • Hook into a SuperPerms-compatible permissions manager (a common task in Bukkit)
  • Be loadable and deployable without Scala anywhere on the target system

This post details the process of converting AntiFreeze into ScalaMC, starting with plain Java and working towards Scala one file at a time. The final product is available at the ScalaMC repository; note that I’ll be describing some changes out of order with respect to the commit history there, for ease of explanation. Furthermore, this post will be written from my perspective: someone with extensive Java experience but virtually no idea of Scala syntax, best practices, or code style. Prior to this project, I had written exactly zero lines of Scala code (and it may show).

Converting code

The first step in building ScalaMC was to actually rewrite all the Java code from AntiFreeze in Scala. Maven recommends the following directory path for source code files (in general, then expanded to ScalaMC’s case):

{project_root}/src/main/{language}/{reverse-package}
ScalaMC/src/main/scala/com/pneumaticraft/scalamc

In the conversion, ScalaMC will keep the same class and package substructure as AntiFreeze. The four classes undergoing conversion are then:

  • AntiFreeze -> ScalaMC: the main plugin file, responsible for loading everything else
  • AFPermissionsHandler -> SMPermissionsHandler: an interface into SuperPerms that abstracts away some messiness from the multiple possible permissions plugins that server admins use
  • AFCommand -> SMCommand: an abstract superclass representing a generic command that users can issue to the plugin
  • AFIceCommand -> SMHelloCommand: the one concrete command that ScalaMC will handle; it managed ice settings in AntiFreeze and will respond to /hello in ScalaMC

For reasons relating to mixed compilation of both Java and Scala files, I chose to convert these files in reverse order; this decision helped simplify some mid-conversion testing and Maven compilation, which I’ll get to later. For now, we’ll start with the AFIceCommand class.

Packages

The first change crops up right away, with the package declaration:

// Java
package com.pneumaticraft.antifreeze.commands;

Beyond changing the package name (from antifreeze to scalamc), Scala package syntax also differs in that it doesn’t like a single package declaration at the head of the file. Instead, packages wrap their enclosed classes and code using curly braces, much like a logical syntax block might:

// Scala
package com.pneumaticraft.scalamc.commands {
    // code here
}

Imports

Next up are the import statements. Scala keeps these very close to Java convention, with the single caveat that semicolons are no longer required:

// Java
import java.util.List;

// Scala
import java.util.List

When researching Scala imports, however, I came across an interesting tidbit: you’re allowed to combine multiple imports from common packages using curly braces. Naturally, the first thing I tried was to combine imports from org.bukkit into a single line:

// Java
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.java.JavaPlugin;

// Scala
import org.bukkit.{ChatColor, command.CommandSender, permissions.PermissionDefault, java.JavaPlugin}

What I didn’t realize at the time was that Scala needs all imports in a combined import statement to be from the same package. The Scala compiler threw an error upon finding a period inside the curly braces above, and so I had to leave the four statements separate.

Class extension and superclass constructors

With imports out of the way, it was time to actually convert the first class declaration. For the sake of this section, I’ll be dealing with the class and its constructor together; we’ll see why in a second. AntiFreeze used the following Java code:

// Java
public class AFIceCommand extends AFCommand {
    public AFIceCommand(JavaPlugin plugin) {
        super(plugin);
        this.setName("Ice"); // more of the same
    }
}

Scala has a lot to say about these six lines. The big issues in Scala here are:

  • Everything is public by default (something that causes confusion sometimes), so we can drop all the public modifiers
  • Constructor arguments are passed in the class declaration, rather than in an explicit constructor method declaration
  • Argument types are given after the argument name, separated by a colon; this is standard Scala syntax for denoting an object’s type
  • Any calls to super are also done in the class declaration, after the extends X clause
  • There is no constructor method explicitly declared; instead, any code to run at instantiation is placed directly inside the class body

Following these rules, the above Java snippet becomes the following:

// Scala
class SMHelloCommand(plugin:JavaPlugin) extends SMCommand(plugin) {
    this.setName("Hello") // more of the same
}

Notice how the constructor disappears entirely and its code is absorbed either into the class declaration or directly into the body of the class.

Methods

The last thing to convert is the sole method runCommand inside the class. This particular method is responsible for responding to an execution of the /hello command, and so differs the most in functionality from its AntiFreeze counterpart; for the sake of illustration, I’ll write only the Java equivalent of what I want ScalaMC to do, then convert it. (The original runCommand body for AntiFreeze is still in GitHub if you want to take a look.)

Basically, this method needs to send a message back to the command issuer that says only "Hello!". In Java and Scala, that would look like:

// Java
@Override
public void runCommand(CommandSender sender, List<String> args) {
    sender.sendMessage("Hello!");
}

// Scala
override def runCommand(sender:CommandSender, args:List[String]) = sender.sendMessage("Hello!")

The most interesting thing here is that Scala method bodies are separated from their declarations with an equals sign; though curly braces are used to indicate a multi-line block as a body, our one-line method here can directly follow the = and be understood. What’s more, the Java generic List<String> is converted to use square brackets (to List[String]) - a minor notational difference, but one that’s important in Scala. Finally, the @Override annotation from Java is folded into the method declaration itself, rather than standing apart.

More complex changes

At this point, ScalaMC has a fully converted SMHelloCommand.scala file ready to go in place of the old AFIceCommand.java file. But all we’ve really done so far is handle syntax changes between the two languages - nothing here truly takes advantage of Scala’s capabilities. By contrast, some changes in later files tend to stray more from the Java code we’ve already written into fresh new Scala concepts.

From here on out, I’ll be presenting only snippets from the other three files that ScalaMC converted; remember you can check out full ScalaMC files on GitHub.

Casting

In Java, programmers will frequently cast objects from one type to another. But in Scala, the type system is a bit stronger, and types are inferred at compilation time; how, then, can we handle a cast? For example, the AFCommand.java file features the following snippet:

// Java
public AntiFreeze getPlugin() {
    return (AntiFreeze) this.plugin;
}

In this case, this.plugin is really an instance of AntiFreeze, but it’s only ever passed to the AFCommand class and stored as an instance of JavaPlugin. The cast is relatively safe, but how do we convince Scala of that? It turns out we use a feature of the language called pattern matching. We can write what amounts to a brief case statement on the type of plugin, returning or erroring appropriately:

// Scala
def getPlugin():ScalaMC = plugin match {
    case plugin: ScalaMC => plugin
    case _ => throw new ClassCastException
}

What’s going on here? First, we declare the type of the method explicitly: the :ScalaMC clause at the end of the method declaration says that the method will return an instance of ScalaMC (the core plugin class). Then, instead of jumping right into a code block, we start off with the phrase plugin match, which means that we’ll be checking properties of the plugin object in each case statement to follow. We can then check if plugin is an instance of ScalaMC (the phrase case plugin: ScalaMC) and return it (the => plugin); otherwise, in the default case, we throw a ClassCastException.

This kind of logic, where entire blocks of code or methods enumerate sequences of cases, is fairly common in functional languages; it relies on the typing system to match a particular case, then returns an expression associated with that case.

MapReduce

This last refactoring is one of my personal favorites from the whole project. In AntiFreeze, the AFPermissionsHandler class has two methods that each check a series of permissions against a particular user: hasAnyPermission and hasAllPermission. The behavior of each method is fairly obvious from the method signature, but the Java implementation is rather extensive:

@Override
public boolean hasAnyPermission(CommandSender sender, List<String> allPermissionStrings, boolean opRequired) {
    for (String node : allPermissionStrings) {
        if (this.hasPermission(sender, node, opRequired)) {
            return true;
        }
    }
    return false;
}

Though we could keep those loops in the Scala transformation, I’ll instead go with an idea born of functional languages: the concept of map-reduce. Here, “map” means the act of applying a function to a sequence, and “reduce” refers to combining elements in that sequence. Both use first-class functions as arguments to apply to a sequence, and together, they allow the transformation of a sequence of elements down to a single return value. The converted Scala:

// Scala
override def hasAnyPermission(sender:CommandSender, allPermissionStrings:List[String], opRequired:Boolean) =
    allPermissionStrings.map((s:String) => this.hasPermission(sender, s, opRequired)).reduceLeft((x,y) => x || y)

Here, we take the allPermissionStrings list and map a function on it that calls hasPermission with each string in sequence (referred to as s in the function). This converts our List[String] down into a List[Boolean], corresponding to whether or not the user in question has each permission in the original list. We then reduce this list of booleans with another function that simply takes the logical-or of any two arguments; the particular flavor of reduce, reduceLeft, starts on the left of the list and works to the right.

By converting this method into a map-reduce application, we’ve drastically shortened it, and made it easy to compute in parallel, since multiple cores can be working on the map step at any given time. (Unfortunately, Bukkit tends not to parallelize much, so I’m not really sure how much this has actually gained us.) There’s even one more optimization we can make: anonymous functions in Scala with only one argument can use an underscore as the argument placeholder, eliminating the need for us to specify the s argument at all in the map:

// Scala
override def hasAnyPermission(sender:CommandSender, allPermissionStrings:List[String], opRequired:Boolean) =
    allPermissionStrings.map(this.hasPermission(sender, _, opRequired)).reduceLeft((x,y) => x || y)

Whether or not this is more readable is up for debate; however, it lets us shorten our method even more.

One last caveat for those of you following the types involved: we specified allPermissionStrings as a List[String], which in this case refers to a java.util.List. However, Java doesn’t have the syntax or methods to support the map call in this function; how, then, are we mapping that function across allPermissionStrings? It turns out Scala has a handy import statement that lets you convert on the fly between Java and Scala collections:

// Scala
import scala.collection.JavaConversions._

With that import, Scala will change allPermissionStrings to a scala.collection.immutable.List before mapping the function across its elements.

Building ScalaMC

Now that the interesting parts of converting ScalaMC have been covered and we have a Scala-only plugin, how do we build it? I mentioned earlier that AntiFreeze used Maven for its build system, and ScalaMC is no different; we just need to tweak the Maven configuration file, pom.xml, to handle some Scala compilation. Note that I am by no means a Maven expert, and so most of these snippets come from other places on the Internet; for me, they supported mixed Java and Scala compilation during the conversion period, and are building ScalaMC just fine as a pure Scala project.

First up, we need to add the Scala language repositories so that Maven understands where to search for Scala tools and code:

<repositories>
    <repository>
        <id>scala-tools.org</id>
        <name>Scala-tools Maven2 Repository</name>
        <url>http://scala-tools.org/repo-releases</url>
    </repository>
</repositories>
<pluginRepositories>
    <pluginRepository>
        <id>scala-tools.org</id>
        <name>Scala-tools Maven2 Repository</name>
        <url>http://scala-tools.org/repo-releases</url>
    </pluginRepository>
</pluginRepositories>

Next, the Scala compiler needs to be brought into the picture. This configuration adds it in the compile, test-compile, and process-resources Maven phases, so that Scala code is compiled whenever it’s needed. Furthermore, it adds a few compiler flags to leave dependency information around so that Java and Scala code can freely intermix; the original post featuring this snippet used the example of a Java class extending a Scala class, which in turn extended another Java class.

<plugin>
    <groupId>org.scala-tools</groupId>
    <artifactId>maven-scala-plugin</artifactId>
    <version>2.15.2</version>
    <executions>
        <execution>
            <id>compile</id>
            <goals><goal>compile</goal></goals>
            <phase>compile</phase>
        </execution>
        <execution>
            <id>test-compile</id>
            <goals><goal>testCompile</goal></goals>
            <phase>test-compile</phase>
        </execution>
        <execution>
            <phase>process-resources</phase>
            <goals><goal>compile</goal></goals>
        </execution>
    </executions>
    <configuration>
        <scalaVersion>${scala.version}</scalaVersion>
        <args>
            <arg>-make:transitivenocp</arg>
            <arg>-dependencyfile</arg>
            <arg>${project.build.directory}/.scala_dependencies</arg>
        </args>
    </configuration>
</plugin>

Finally, there’s that one last pesky requirement: that the target system not have Scala installed, yet still be able to run the plugin. The answer to that problem lies in JarJar, a project that allows the flattening of multiple jars into a single “über-jar” for distribution. In ScalaMC, this means we’ll be flattening the compiled plugin and the scala-library.jar file that comes with a full Scala distribution into one, so that ScalaMC can depend on Scala classes from within its own jar. The configuration looks like this:

<plugin>
    <groupId>org.sonatype.plugins</groupId>
    <artifactId>jarjar-maven-plugin</artifactId>
    <version>1.5</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals><goal>jarjar</goal></goals>
            <configuration>
                <includes>
                    <include>org.scala-lang:scala-library</include>
                </includes>
                <rules>
                    <rule>  
                        <pattern>scala.**</pattern>
                        <result>com.pneumaticraft.scalamc.inject.scala.@1</result>
                    </rule>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

Note that JarJar uses some bytecode transformations to change all references to scala.* classes and packages to start with com.pneumaticraft.scalamc.inject.scala; this is to avoid conflict with other classes that may occupy the scala.* namespace in the target system.

All these changes require one last fix in the pom.xml file, to introduce a formal dependency on Scala:

<dependencies>
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
        <version>2.9.2</version>
    </dependency>
</dependencies>

With that, we should be good to go: a run of mvn clean package produces an output plugin file ScalaMC-0.1.jar that can be dropped straight into a vanilla Bukkit server and run successfully!

Final thoughts

This project was mostly about demonstrating the feasibility of using Scala in a system that, until now, has been almost entirely plain Java. I knew virtually nothing about Scala going in; judicious Googling and help from StackOverflow solved most problems very handily, and though I’m sure there are quite a few things I could have (and can get) done better, I’m fairly comfortable with the state of ScalaMC as it stands. I’ll probably continue to refactor the project and use it as a baseline for other Scala plugins; other Bukkit developers are free to fork the GitHub repo and work with it as well.

ScalaMC also turned me on to Scala as a language that has some great potential over Java: the combination of allowing lots of Java syntax and structures together with functional features like maps and lambdas could be a very powerful thing. On the other hand, the TIOBE Index has had Scala hovering around #46 in popularity since 2007, and has noted the only major new language to enter the top 10 in recent years is Objective-C; with that in mind, and with the amount of legacy Java code still sitting around, Scala may not be ready to break into prime time just yet. Regardless, it’s been an incredibly interesting side project, and I aim to continue exploring Scala in the future.