Hacking with Handlebars in Java and Clojure: Part II

Pranav Gajjewar
Aug 23 · 6 min read

In Hacking with Handlebars in Java and Clojure: Part I, we took a look at how to extend the Handlebars.java library in Java and then in Clojure.

We used proxy in Clojure to extend an abstract class. In that way, we implemented a Clojure version of a TemplateLoader class in Handlebars.java.

In this post, I want to discuss some more ways in which this can be accomplished. We will look at some interesting ways to do Clojure-java interop.

Knowledge about the previous post or about Handlebars.java is not strictly necessary since the things we discuss can be applied to any scenario where you are trying to extend/use Java code in a Clojure project.

So let’s get started!

Recap

A brief recap on the problem we were trying to solve in Part I of this post. We wanted to store template files on the S3 bucket (which is not directly supported by Handlebars.java for some reason). Hence, we had to write our custom logic in the form of a TemplateLoader class.

Without going into fine details, what we are trying to accomplish is either extend an Abstract Class from Handlebars.java or implement a TemplateSource interface.

In the rest of the post, we will look at different ways this can be accomplished in Clojure.

1. Java in Clojure Project

One very simple solution on how to extend Java library in Clojure project. Just write Java code! That’s it.

You can write the necessary Java code and directly include it in your Clojure project. I will demonstrate quickly how this is possible with leiningen.

In the earlier post, I wrote a HTTPTemplateLoader java class to show how to write custom loaders in Java and later we implemented the same in Clojure in proxy.

But let’s see how we can just drag and drop that Java implementation in a Clojure project.

HTTPTemplateLoader in Java

Here’s what I did —

  • Create a java dir in your Clojure project’s src dir.
  • In project.clj, add the following config.
:java-source-paths ["src/java"]
  • In the java dir, I added my Java code as follow:
java
└── com
└── github
└── jknack
└── handlebars
└── io
└── HTTPTemplateLoader.java

And that’s it!

Whenever you run any new repl or build using lein, it will compile our Java code.

Now, we can directly use it in our Clojure code using the traditional Java interop:

=> (import com.github.jknack.handlebars.io.HTTPTemplateLoader)=> (def loader (HTTPTemplateLoader. "http://s3-url-prefix/"))

Reference:

2. Using reify

In the previous post, we used proxy in order to extend an Abstract class URLTemplateLoader in our Clojure code.

But there is another way we can implement a similar solution. By using reify in Clojure.

reify can be used to define implementation for a protocol or an interface.

The reify docs also say:

But reify has certain constraints as well. We can only extend protocol or interfaces. So it is not possible for us to extend an Abstract or Base class using reify. But instead, it can be used to implement Java interfaces.

Fortunately for us, Handlebars.java also contains TemplateLoader interface. Instead of extending any Abstract class, we can directly implement this interface using reify for our custom TemplateLoader.

As we saw in the previous post, we need to implement the resolve method which gives us a custom logic on how to construct the template file-paths.

We are doing the same thing here by implementing the TemplateLoader interface from Handlebars.java.

Then we can use the loader class instance returned by reify in the same way, we used http-loader loader instance returned by proxy.

A rule of thumb: Use reify everywhere except where it is prohibitive.

References:

3. Using gen-class

There is a fundamental difference between method #1 and method #2 we discussed above.

reify is purely a runtime implementation of the TemplateLoader interface. But the Java implementation we did with HTTPTemplateLoader gets compiled and we import the compiled class in our Clojure code for interop.

There are also a lot of constraints on extending interfaces using reify. It is a good option when you can work within those constraints.

There is a way we can have the best of both worlds. We can have an implementation/extension of a Java interface or class written purely in Clojure. And it would get compiled and we can use it as a Java class at runtime in Clojure!

Essentially, we are writing the same class as the Java class we wrote before but instead in Clojure!

This is possible by using gen-class. Let’s see how it works.

Suppose, we want to create a Java class that implements the TemplateLoader interface.

Let’s go over the above code, bit by bit.

(ns clj-handlebars.http-loader
(:gen-class
:init init
:state state
:name com.github.jknack.handlebars.io.HTTPCljLoader
:implements [com.github.jknack.handlebars.io.TemplateLoader]
:constructors {[String] []}
:prefix "-")
(:import [com.github.jknack.handlebars.io URLTemplateSource]
[java.net URL]))

In this, we are adding some metadata to our ns declaration.

  1. gen-class: Tells that this namespace should be compiled and generate a class.
  2. init: name of the constructor method. Yes, it’s the same as a Java constructor.
  3. state: A variable in which you can track the state in your Java class instance.
  4. name: The name of your Java class.
  5. constructors: Constructor signatures. If you want overloaded constructors, you can do that using the multi-arity init function.
  6. prefix: Prefix used to map the methods. All the overloaded methods in your class should be prefixed with this value. This is why in the above code, we have the method name -resolve since we want to implement the resolve method in our TemplateLoader interface.

In the rest of the code, we implement each required method from the TemplateLoader interface (prepended with the prefix we specified in metadata).

In our constructor -init method, it accepts one param which we set as the prefix. The value returned by the -init method is accessible inside the rest of the functions using the state variable (or whatever you name it in metadata).

Each method you write needs the first param as this which (as you might have guessed) is the same as Java’s this. The state can be accessed using this.

But how to use this code in our regular Clojure code?

That requires that this code should be compiled to generate a Java class before we can import it into our regular Clojure code. Can we call it compiling this class ahead of time?

And that is essentially Ahead of Time Compilation concept in Clojure. It means compiling some things before we compile/run any of the regular stuff.

But we need to tell this to our build tool as well. We can do this in lein by adding the following to our project.clj

:aot [clj-handlebars.http-loader]

Now, in your regular code, you can just import the Java class generated by the Clojure code.

=> (import com.github.jknack.handlebars.io.HTTPCljLoader)=> (def loader (HTTPCljLoader. "http://s3-url-prefix/"))

Whenever you run a lein repl, this class will be compiled before (so it can be imported by our regular code). This also means that any changes in this Clojure file need a restart of the repl.

And that’s how you can generate java class extending/implementing any interface or class directly from Clojure.

References:

So these are some ways in which you can extend any Java library and use it in Clojure.

Hopefully, you learned something new from this post. I know I did ;)

Thank you!

helpshift-engineering

Engineering blog for Helpshift