Adding Awesomeness to your Legacy Java projects

22.03.2014 Permalink

Suppose you are an enthusiastic Clojure developer, but unfortunately you have to work with plain old Java for a living. You managed to convince your team mates that Clojure would actually make quite some things simpler. Now how can you add Clojure to your Java code base without disruption?

TL;DR see the sample project on Github for a working example.

The required steps are independent of your IDE or build system, but to make the description concrete I assume the following: And here we go:

In Eclipse install Counterclockwise plugin

Go to Help / Install new software and add the link to the update site. You can use any name, I prefer "CCW". Check the Counterclockwise item under Clojure programming and hit Next. Follow the instructions.

Counterclockwise allows you to edit Clojure code and nicely interact with a REPL. If you enable strict Paredit mode in Eclipse preferences and reorder the Eclipse views a bit, you will get a tool that allows for the desired flow of interactive development that Lisp is famous for. (I admit Emacs is my favorite editor, but introducing Clojure PLUS Emacs in your Java organisation could be asking too much of your co-workers.)

Drop Clojure and other required libs into your pom.xml

The language Jar takes less than 3.5 MB, and additional Clojure libraries are tiny compared to their Java counterparts. So size is certainly not an issue. Here's a minimal pom.xml to get you started:
<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
	                    http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>de.friemen.samples</groupId>
	<artifactId>legacy-java</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<dependencies>
		<dependency>
			<groupId>org.clojure</groupId>
			<artifactId>clojure</artifactId>
			<version>1.6.0-RC2</version>
		</dependency>
	</dependencies>
</project>
Counterclockwise automatically adds Clojure support to your project right after adding the Clojure dependency to the pom.xml. To find out the latest version of Clojure you should use Mavens Central search page.

In order to find additional libraries only hosted on Clojars you can add a corresponding repository entry:
	<repositories>
		<repository>
			<id>clojars.org</id>
			<url>http://clojars.org/repo</url>
		</repository>
	</repositories>
For each Clojure library you want to use, you have to add a dependency to your pom.xml, for example like this:
	        <dependency>
			<groupId>org.clojure</groupId>
			<artifactId>java.data</artifactId>
			<version>0.1.1</version>
		</dependency>

Create your own namespace to add custom Clojure code (optional)

You can include Clojure and some libraries and use them without writing your own Clojure code. In order to add your own awesome functions you can right-click the target Java package in the Package Explorer and choose New / Other / Clojure Namespace. Enter the unqualified name of your new namespace. Counterclockwise will add the Java package as prefix to the toplevel ns form.

Here is some sample Clojure code for demonstration purposes:
(ns de.friemen.samples.awesome)

(defn hello-world [] 
  (println "Hello World"))

(defn map-inc
  [ints]
  (map inc ints))

(defn sum-by 
  [f employees]
  (reduce (fn [sums e]
            (update-in sums [(f e)] #(+ (or % 0) (:salary e))))
         {}
         employees))
You can develop this in the same way that you would take in a pure Clojure setup.

Call Clojure functions from Java

Clojure 1.6 provides a neat official API to make sure Java code, which directly depends on Clojure functions, won't break in the future. The API contains only two static methods: var and read. The first returns a var from a namespace, the second evaluates an EDN String and returns its Java representation.

This API is already sufficient for simple scenarios, so I could stop here... though, if this would be extensively used in numerous Java classes, maintainability would certainly suffer. In addition, Alex Miller (@puredanger) pointed me to potential performance problems, which can be solved by caching. So read on, if you want to see how I tackle these problems.

First I added a small convenience class on top of clojure.java.api.Clojure:
package de.friemen.samples;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import clojure.java.api.Clojure;
import clojure.lang.IFn;

public class Cljns {
	public final static String CLOJURE_CORE = "clojure.core";
	private final Map<String, IFn> cachedFns = 
	                         Collections.synchronizedMap(new HashMap<String, IFn>());
	private final Map<String, Object> cachedKeywords =
	                         Collections.synchronizedMap(new HashMap<String, Object>());
	protected final String ns; 
	protected final static IFn REQUIRE = Clojure.var(CLOJURE_CORE, "require");
	protected final static IFn DEREF = Clojure.var(CLOJURE_CORE, "deref");
	public final static Cljns core = new Cljns(CLOJURE_CORE);
	
	public Cljns (String ns) {
		this.ns = ns;
		REQUIRE.invoke(Clojure.read(ns));
	}
	
	public String getName() {
		return ns;
	}
	
	public IFn fn(String symbolName) {
		IFn f = cachedFns.get(symbolName);
		if (f == null) {
			f = Clojure.var(ns, symbolName);
			cachedFns.put(symbolName, f);
		}
		return f;
	}
	
	public Object deref(String symbolName) { 
		return DEREF.invoke(fn(symbolName));
	}
	
	public Object keyword(String s) {
		final String kwKey = s.startsWith(":") ? s : ":" + s;
		Object kw = cachedKeywords.get(kwKey);
		if (kw == null) {
			kw = Clojure.read(":" + s);
			cachedKeywords.put(kwKey, kw);
		}
		return kw;
	}
}
The caching with the synchronized map produces an average speed-up of factor 10. If you plan to extensively call Clojure code from Java then caching is a must. Please note that the cache is bound to an instance of Cljns, so these instances should be kept as well, for example as application scoped singletons in your Spring context.

With a static import to Cljns.core, here's the simplest way to call a clojure.core function:
  
	// access clojure.core lib
	core.fn("println").invoke("Hello Awesomeness!");
				
Of course we can make it a little bit more useful, still sticking to clojure.core:
		
	final List<Integer> output = 
			(List<Integer>) 
			core.fn("map").invoke(
				core.fn("inc"), 
				new Integer[] { 1, 2, 3});
		
I admit it's a bit cumbersome compared to the succinct Clojure version (map inc [1 2 3]), so we certainly don't want to see this type of Java code scattered all over our code base.

Instead we write expressions of this kind as Clojure functions in our own namespace (as shown above). In order to invoke them we need the namespace and use it to call one of the contained functions:
  
	// adhoc instantiate a namespace by name
	final Cljns awesome = new Cljns("de.friemen.samples.awesome");
	
	// invoke a function of this namespace
	final List<Integer> output = 
			(List<Integer>)
			awesome.fn("map-inc").invoke(Arrays.asList(1, 2, 3));

This looks ok for simple cases where collections with scalar data types like String or int are used. But how can we pass in a collection of Java Beans and expect for example a Java Map as result? Of course our own Clojure function could expect Java beans and collections but this would considerably harm our ability to write idiomatic Clojure code and make efficient use of the REPL.

To combine idiomatic Clojure code with convenient invocation from Java we need two things: 1) a transformation of Java Beans to/from Clojure data, and 2) a convenient wrapper around our own functions that applies the conversion as necessary. The next snippet shows how we like to call this wrapper:

	// use a dedicated class to hold real Java wrappers for more convenience
	final Awesome awesome = new Awesome();
	final Map<String, Integer> result = awesome.sumBy("branch", 
			Arrays.asList(
				new Employee("Donald Duck", "Bonn", 150),
				new Employee("Daisy Duck", "Bonn", 200),
				new Employee("Mini Mouse", "Luenen", 300),
				new Employee("Micky Mouse", "Luenen", 50)));

This is indeed normal Java code. We have the power of Clojure underneath, but the invocation looks like good old Java. To make this happen we extend Cljns with a new class Awesome, which holds our wrapper method:
package de.friemen.samples;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import clojure.lang.LazySeq;

public class Awesome extends Cljns {
	private static Cljns data = new Cljns("clojure.java.data");
	
	public Awesome() {
		super("de.friemen.samples.awesome");
	}
		
	@SuppressWarnings("unchecked")
	public Map<String, Integer> sumBy (String field, List<Employee> es) {
		return (Map<String, Integer>) 
				fn("sum-by").invoke(
						keyword(field), 
						data.fn("from-java").invoke(es));
	}
}
The conversion between Java objects and Clojure data is done with the clojure.java.data library. It offers the functions to-java and from-java which handle the conversion for us.

Equipped with the explicit Java-Clojure adaption layer that the Awesome class represents, we are now able to create awesome Clojure functions and conveniently call them from Java. I don't advocate to wrap every single Clojure function like this, in simple cases it doesn't pay off. Keeping the function name in a final String field is sufficient in most cases to save us from shotgun surgery when function names change.

I pushed the complete Java project with sample code to GitHub.