Some notes on building Project Lambda to test closures and defender methods in Java

Posted on Thu 15 December 2011 in Coding

Check out Project Lambda if you don’t already know what I’m talking about! It adds lambda functions and interface extension methods to Java. Lambda functions allows for very compact and concise code as well as functional programming aspects. Interface extension methods gives you the ability to add to interfaces without breaking implementing classes (and can also be seen as a poor man’s multiple inheritance, which in turn opens up for mixins).

These things already exist in the .NET world and are very powerful, so I wanted to try them out in Java. As of this writing, though, the binary snapshot available here does not support defender methods. You have to build everything from source.

Anyway, below are some notes on the build process. The full readme can be found here.

Update 2011-12-15: I created a script that performs the steps below minus installing prerequisites.

Operating System

Just to set the context:

$ uname -a
Linux devel 2.6.38-8-server #42-Ubuntu SMP Mon Apr 11 03:49:04 UTC 2011 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=11.04
DISTRIB_CODENAME=natty
DISTRIB_DESCRIPTION="Ubuntu 11.04"

I couldn’t get the build to work on another machine running Oneric, though. The linker wouldn’t link against libasound for some reason.

Install prerequisites

Some of these are listed in the readme linked above. YMMV since you probably already have some of the packages. Here are the ones I had to install:

zip unzip libasound2-dev libcups2-dev libfreetype6-dev gawk libxext-dev libxrender-dev libxtst-dev

Mercurial, Java and Ant are essential. :-) You need at least JDK 1.7.0 to build things, and I found it quite painful to install Java 1.7 using Ubuntu’s own package system. I think the problem was that I installed Java before Ant, and Ant dragged in Java 6 which changed all the alternatives symlinks. In the end, I downloaded the JDK from Oracle, unpacked it to /opt/jdk1.7.0 and updated my PATH environment variable to include /opt/jdk1.7.0/bin before anything else.

Download defender-prototype

Defender-prototype implements the weaving necessary for defender methods to work in the JVM. The build script for Project Lambda expects defender-prototype to exist alongside Lambda’s own directory.

$ hg clone http://hg.openjdk.java.net/lambda/defender-prototype

Download defender-prototype required libraries

These are not included in the repository, so manual download is needed. Feel free to select a different mirror for ASM from here (if there is one, that is).

$ cd defender-prototype
$ mkdir lib && cd lib
$ wget http://testng.org/testng-6.2.zip
$ unzip -e -j testng-6.2.zip testng-6.2/testng-6.2.jar
$ wget http://download.forge.objectweb.org/asm/asm-4.0-bin.zip
$ unzip -e -j asm-4.0-bin.zip asm-4.0/lib/asm-4.0.jar
$ rm asm-4.0-bin.zip testng-6.2.zip
$ cd ..

Build defender-prototype

Very simple:

$ ant

Download the Project Lambda source code

Two-step process:

$ hg clone http://hg.openjdk.java.net/lambda/lambda
$ cd lambda
$ sh get_source.sh

Setup the environment

JAVA_HOME must be unset, while ALT_BOOTDIR should point to the JDK directory. Here’s how to find the real JDK directory in case you’re using a Ubuntu package:

$ readlink -f $(dirname $(readlink -f $(which javac)))/..
/opt/jdk1.7.0

We also want the build scripts to download required dependencies for us, so ALLOW_DOWNLOADS should be set to true. And the build script requires LANG to be set to C. Thus:

$ unset JAVA_HOME
$ export ALT_BOOTDIR=/opt/jdk1.7.0
$ export ALLOW_DOWNLOADS=true
$ export LANG=C

Perform partial sanity check

Partial, because it unfortunately doesn’t check all prerequisites. The sanity check is run when you build (below), but it might be useful to run it separately:

$ make sanity

Build and install

Very simple also, but takes some time… Check back periodically for missing prerequisites!

$ make all

Installation is a question of copying the shiny new JDK somewhere. For example:

$ sudo cp -a build/linux-amd64/j2sdk-image /opt/jdk8lambda

(If you’re on a different architecture, change linux-amd64 as needed.)

Test it!

Let’s test both closures and defender methods at the same time:

// TestLambda.java
import java.util.concurrent.Callable;

public class TestLambda {
        public static String sayHi(A a) {
                return "Hello from " + a;
        }

        public static interface A {
                String sayHi() default { return sayHi(this); }
        }

        public static class AImpl implements A { }

        public static void main(String[] args) throws Exception {
                Callable<String> c = () -> new AImpl().sayHi();
                System.out.println(c.call());
        }
}

Note that the AImpl class is empty! Also note the lambda operator ->, which is similar to the one in C#, => (and identical to the one in F# :-)).

Since Project Lambda doesn’t introduce functional interfaces, we have to use existing interfaces. In the example above, I chose to use Callable<String> as something that takes no arguments and returns a String value. A consequence of this choice is that I have to let the main method throw all exceptions.

Compile and run:

$ /opt/jdk8lambda/bin/javac TestLambda.java
$ /opt/jdk8lambda/bin/java TestLambda
Hello from TestLambda$AImpl@46bd530

Very nice indeed! :-)

Update 2011-12-16: As pointed out to me on the lambda-dev mailing list, defender methods are not supported by the JVM just yet, so that the case above works anyway is a “lucky accident”. Therefore the class files should be statically woven before running:

$ /opt/jdk8lambda/bin/javac TestLambda.java
$ /opt/jdk8lambda/bin/java -cp ../defender-prototype/distrib/jsr335-agent.jar:../defender-prototype/lib/asm-4.0.jar jsr335.agent.Main -d woven --jdk /opt/jdk8lambda TestLambda*.class
$ cd woven
$ /opt/jdk8lambda/bin/java TestLambda
Hello from TestLambda$AImpl@46bd530