Just An Application

September 25, 2012

Building The Project Lambda JDK From Source On Mac OS X Lion

If you are interested in the work being done by the JSR 335 working group on defining Lambda Expressions for Java and you would like to get a feel for how they are going to work in practice you can either download a binary release of the Project Lambda version of the JDK which includes the reference implementation or, if you are feeling adventerous or possibly lucky, you can build it from source.

As you can tell from the title of this post I went with option two.

1.0 Prerequisites

1.1 Mac OS X Lion

JDK 7 is required to build the Project Lambda version of the JDK and the version of JDK 7 available for download will only install on Lion, and I assume Mountain Lion.

I started by trying to build on Snow Leopard using a locally built version of the Mac OS X OpenJDK port but ran into a few problems during the building of the javax.nio buffer code. The problems do not look as though they are insurmountable, but for the moment I am not able to build the Project Lambda JDK on Snow Leopard.

I have not done so but I would assume that it also possible to build the Project Lambda JDK on Mountain Lion.

1.2 JDK 7

The Project Lambda JDK needs to be built using JDK 7. You can get JDK 7 for Mac OS X Lion here.

1.3 Project Lambda JDK Source Code

You can clone the Project Lambda Mercurial repository using the command.

    hg clone http://hg.openjdk.java.net/lambda/lambda lambda

This should result in something like the following output

    ...

    requesting all changes
    adding changesets
    adding manifests
    adding file changes
    added 502 changesets with 536 changes to 85 files
    updating working directory
    82 files updated, 0 files merged, 0 files removed, 0 files unresolved

Once you have done this you need to clone the nested Mercurial repositories which comprise the JDK itself.

You can do this by changing to the directory into which you cloned the Project Lambda Mercurial repository, if you used the command above that will be the lambda directory, and then running the command.

 sh ./get_source.sh

Once you have got all the source you can do the build.

2.0 Building

There are currently two build systems for the JDK. The existing one and a new one being developed for use when building JDK 8.

Following advice given on the lambda-dev mailing list I went with the new one.

You need to start by changing to the directory common/makefiles

    cd common/makefiles

and then perform a configuration step

    sh ../autoconf/configure --with-boot-jdk=/Library/Java/JavaVirtualMachines/jdk1.7.0_07.jdk/Contents/Home

The --with-boot-jdk argument identifies the location of the JDK 7 binaries, libraries and JARs, etc.. JDK 7 is installed as a Mac OS X package and the given path points inside the package.

Running the command should produce a lot of output ending with something like the following which has been edited slightly for clarity

    ...

    ===================================================
    A new configuration has been successfully created in \
        elided/lambda/build/macosx-x64-normal-server-release
    using configure arguments '--with-boot-jdk=\
       /Library/Java/JavaVirtualMachines/jdk1.7.0_07.jdk/Contents/Home'.

    Configuration summary:
    * Debug level:    release
    * JDK variant:    normal
    * JVM variants:   server
    * OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64
    * Boot JDK:       /Library/Java/JavaVirtualMachines/jdk1.7.0_07.jdk/Contents/Home

    Build performance summary:
    * Cores to use:   2
    * Memory limit:   4096 MB
    * ccache status:  not installed (consider installing)

    Build performance tip: ccache gives a tremendous speedup for C++ recompilations.
    You do not have ccache installed. Try installing it.

Once the configuration has completed you simply run the command

    make images

This should start by producing the output

    Building OpenJDK for target 'images' in configuration 'macosx-x64-normal-server-release'

and complete some time later with something like the following

    ...

    ##### Leaving jdk-images for target(s) images-only                 #####
    ########################################################################
    ##### Build time 00:00:48 jdk-images for target(s) images-only     #####
    ########################################################################

    -- Build times ----------
    Target 
    Start 2012-09-25 00:11:17
    End   2012-09-25 00:22:38
    00:00:28 corba
    00:02:45 hotspot
    00:00:26 jaxp
    00:00:31 jaxws
    00:05:43 jdk
    00:00:48 jdk-images
    00:00:40 langtools
    00:11:21 TOTAL
    -------------------------

If the build is successful then the resulting JDK can be found in the directory

    build/macosx-x64-normal-server-release/images/j2sdk-image

Unlike JDK 7 it is not currently built as a Mac OS X package.

3.0 Testing The Build

To check whether I had a functioning build at least as far as Java Lambda Expressions were concerned I wrote three very simple pieces of code.

All of them use the forthcoming java.util.streams.Stream interface because it defines lots of methods which can be passed Lambda Expressions.

All of them actually work as expected when compiled and run using the Project Lambda JDK that I built.

3.1 Echo

    package xper.lambda;

    import java.util.Arrays;

    public final class Echo
    {
        public static void main(String[] theArgs)
        {       
            new Echo().run(theArgs);
        }
    
        //
    
        private Echo()
        {
        }
    
        //
    
        private void run(String[] theArgs)
        {
            first = true;
            Arrays.
                stream(
                    theArgs).
                forEach(
                    s -> { 
                             if (!first) 
                             { 
                                 System.out.print(' '); 
                             }
                             else
                             {
                                 first = false;
                             }
                             System.out.print(s);
                         });
            System.out.println();
        }
    
        //
    
        private boolean first;
    }

The code creates a Stream from the program’s arguments using the Arrays.stream() utility method and then uses the Stream’s forEach() method to apply a Lambda Expression to each of the arguments.

The forEach() method is declared like this

    void forEach(Block<? super T> block);

and the Block interface is defined in the java.util.functions package like this

    public interface Block {

    /**
     * Performs operations upon the provided object which may modify that object
     * and/or external state.
     *
     * @param t an input object
     */
    void apply(T t);

    /**
     * Returns a Block which performs in sequence the {@code apply} methods of
     * multiple Blocks. This Block's {@code apply} method is performed followed
     * by the {@code apply} method of the specified Block operation.
     *
     * @param other an additional Block which will be chained after this Block
     * @return a Block which performs in sequence the {@code apply} method of
     * this Block and the {@code apply} method of the specified Block operation
     */
    public Block<T> chain(Block<? super T> other) default {
        return (T t) -> { apply(t); other.apply(t); };
    }
}

At first glance it is not entirely obvious why the forEach() method accepts a Lambda Expression at all, but the
Block interface is what JSR 335 defines as a functional interface

A functional interface is an interface that has just one abstract method, and thus represents a single function contract. (In some cases, this “single” method may take the form of multiple abstract methods with override-equivalent signatures inherited from superinterfaces; in this case, the inherited methods logically represent a single method.)

In addition to the usual process of creating an interface instance by declaring and instantiating a class, instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

Note that although the Block interface declares two methods it is still a functional interface because only one of them is abstract. The other is a default method which defines an implementation of the method despite appearing in an interface declaration. This is another new feature defined by JSR 335.

A default method is a method that is declared in an interface and that has a block body prefixed by the keyword default. Its body provides a default implementation for any class that implements the interface without overriding the method. This allows new functionality to be added to existing (and perhaps already widely-distributed) interfaces. More generally, it provides a mechanism for multiple inheritance of behavior.

In this example the Lambda Expression passed to the forEach() can be made to match the definition of the Block
interface by the compiler.

3.2 ToUpper

    package xper.lambda;

    import java.util.Arrays;

    public final class ToUpper
    {
        public static void main(String[] theArgs)
        {       
            new ToUpper().run(theArgs);
        }
    
        //
    
        private ToUpper()
        {
        }
    
        private void run(String[] theArgs)
        {
        first = true;
        Arrays.
            stream(
                theArgs).
            map(
                s -> s.toUpperCase()).
            forEach(
                s -> { 
                         if (!first) 
                         { 
                             System.out.print(' '); 
                         }
                         else
                         {
                             first = false;
                         }
                         System.out.print(s);
                     });
        System.out.println();
        }
    
        //
    
        private boolean first;
    }

This code is a modified version of the Echo code above. The map() method is used to covert each of the program’s argument to upper case before they are printed out.

The map() method is declared like this

    <R> Stream<R> map(Mapper<? super T, ? extends R> mapper);

and the Mapper interface is declared in the java.util.functions package like this

    public interface Mapper<T, R> {

        /**
         * Map the provided input object to an appropriate output object.
         *
         * @param t the input object to be mapped.
         * @return the mapped output object.
         */
        R map(T t);

        /**
         * Combine with another mapper producing a mapper which preforms both
         * mappings.
         *
         * @param  Type of output objects from the combined mapper. May be the
         * same type as {@code <U>}.
         * @param after An additional mapping to be applied to the result of this
         * mapping.
         * @return A mapper which performs both the original mapping followed by
         * a second mapping.
         */
        public <V> Mapper<T, V> compose(Mapper<? super R, ? extends V> after) default {
            return Mappers.chain(this, after);
        }
     }

Like the Block interface above it is a functional interface which is why the Lambda Expression

    s -> s.toUpperCase()

can be passed to the map() method.

3.3 Sum

    package xper.lambda;

    import java.util.Arrays;

    public final class Sum
    {
        public static void main(String[] theArgs)
        {       
            System.out.print("sum == ");
            System.out.println(
                Arrays.
                    stream(
                        theArgs).
                    map(
                        s -> Integer.parseInt(s)).
                    reduce(
                        0, 
                        (i, j) -> i + j));
        }
    }

This code relies heavily on auto-boxing and unboxing and type inference by the compiler and is an example of how gnomic it is possible to be when using Lambda Expressions.

It uses the map() method to produce a Stream of Integers and the reduce() method to sum them.

The reduce() method is declared like this

    T reduce(T base, BinaryOperator<T> op);

and the BinaryOperator interface is declared in the java.util.functions package like this

   public interface BinaryOperator<T> extends Combiner<T,T,T> {

        public T operate(T left, T right);

        @Override
        T combine(T t1, T t2) default {
            return operate(t1, t2);
        }
    }

Like the Block and Mapper interfaces this is a functional interface which is why it is possible to pass the Lambda Expression

    (i, j) -> i + j

to the reduce() method.

The resulting program behaves as expected.

For example, the command

    java xper.lambda.Sum 1 2 3 4 5 6

produces the output

    sum == 21

Copyright (c) 2012 By Simon Lewis. All Rights Reserved.

Blog at WordPress.com.