Nuts and Bolts Blending Java Lead image: Lead Image © Ivan Mikhaylov, 123RF.com
Lead Image © Ivan Mikhaylov, 123RF.com
 

Blending Java with other programming languages

It's in the Blend

Java is not just about beans, it's also about the huge variety of libraries and frameworks that keep the language alive. If you feel like a bit of blending, Java integrates many flavors of third-party languages. By Bernhard Bablok

Neither project managers nor programmers mix languages purely for pleasure. The former are worried about the complexity and the increased demands on their team, while the latter prefer to use things that they feel most comfortable with – and that is precisely one programming language for most of them. Having said this, the second programming language often does more good than harm in a software project:

Java is a very powerful language, so it comes as no surprise that it also supports integration of software written in third-party languages for any use case. The following sections look into these techniques, along with their benefits and drawbacks.

Nothing Works Without C

One reason for Java's success is that it protects the developers from a few tricky things that were required in C and C++, in particular dynamic memory management. Despite this, the makers of Java saw from the outset that Java would have difficulty surviving in the real world without being able to access the versatile resources of existing C libraries. Thus, even the very first Java version offered the option of integrating C code in the form of the Java Native Interface (JNI).

Java access to C relies on two layers (Figure 1). At the top is a lean Java class, which does little more than describe the interface and load the next layer by means of a static method. From a technology point of view, the second layer is a dynamic library (.so file, or a DLL on Windows) that converts Java calls to C.

Java accesses C code via layers.
Figure 1: Java accesses C code via layers.

Here is a practical example: In one of my projects [1], the task was to make the Readline library available in Java. Native Java programs cannot pick up output from stdout. In contrast, Readline can output a line in which the user can navigate with keystrokes and also edit in a style similar to Bash (Figure 2).

Thanks to Readline, the input from the previous command can be easily edited.
Figure 2: Thanks to Readline, the input from the previous command can be easily edited.

The library is used, for example, in Jython [2]; Jython implements the Python language in Java. Listing 1 shows part of the class definition. For design reasons, all of the native methods are private; in other words, there is a private native String readineImpl() (line 34) for the public String readline() method (line 17). The special thing about this class is that it is not abstract; however, it still only defines one interface for these native methods. The load() (lines 10-13) loads the native library.

Listing 1: Snippet of the Readline.java Class

01 package org.gnu.readline;
02
03 import java.io.*;
04 import java.util.*;
05
06 public class Readline {
07
08 [...]
09
10   public static final void load(ReadlineLibrary lib) throws UnsatisfiedLinkError {
11     [...]
12     System.loadLibrary(lib.getName()); // might throw UnsatisfiedLinkError
13   }
14
15   [...]
16
17   public static String readline(String prompt, boolean addToHist)
18    throws EOFException, IOException, UnsupportedEncodingException {
19     [...]
20     String line = readlineImpl(prompt);
21     if ((line != null) && (addToHist)) {
22       addToHistory(line);
23     }
24     return line;
25   }
26
27   public static void addToHistory(String line) {
28     [...]
29     addToHistoryImpl(line);
30   }
31
32   [...]
33
34   private native static String readlineImpl(String prompt)
35    throws EOFException, UnsupportedEncodingException;
36
37   private native static void addToHistoryImpl(String line);
38   [...]
39 }

The javah tool provided by the JDK generates the C header file, org_gnu_readline_Readline.h, from the bytecode of the class:

javah -classpath $(BUILDDIR) -jni org.gnu.readline.Readline

Listing 2 shows a snippet of this. The actual implementation then involves converting the Java types passed in – such as jstring to char* – into a format that the C library understands. There are a couple of pitfalls here, especially on non-Unicode systems.

Listing 2: Snippet of the C Header Generated

01 /* DO NOT EDIT THIS FILE - it is machine generated */
02 #include <jni.h>
03 /* Header for class org_gnu_readline_Readline */
04 [...]
05 /*
06  * Class:     org_gnu_readline_Readline
07  * Method:    initReadlineImpl
08  * Signature: (Ljava/lang/String;)V
09  */
10 JNIEXPORT void JNICALL Java_org_gnu_readline_Readline_initReadlineImpl
11   (JNIEnv *, jclass, jstring);
12
13 /*
14  * Class:     org_gnu_readline_Readline
15  * Method:    cleanupReadlineImpl
16  * Signature: ()V
17  */
18 [...]

Magic of Images

Web applications implemented in Java that make intensive use of image editing features frequently rely on JMagick [3]. JMagick is a Java-C interface for ImageMagick, and it's a good example of why JNI programming can be a frustrating experience. One issue here relates to stability across the board. Integrating buggy C code injects atypical problems into your application – very often crashes or memory leaks that gradually drag down the application server.

A second problem is inherent to ImageMagick, whose developers often change the interfaces. That means the JMagick wrapper works only with precisely the ImageMagick version for which it was built. Administrators either need to install a version of ImageMagick that does not match their choice of distribution or compile the JMagick version for precisely the current ImageMagick ABI; however, this involves code changes to JMagick and makes maintaining the code base accordingly difficult.

An alternative called Im4java [4], which is another of my projects, does without the performance benefits of JNI and instead offers a stable, object-oriented interface to ImageMagick. You can integrate this by directly calling the ImageMagick executable (typically convert) via the ProcessBuilder class. This means that the non-Java code runs in a separate process and can never cause any damage.

Scripting

As a compiled, high-level language, Java lacks the benefits of scripting. Some attempts have been made to compensate for these drawbacks. As a result, both approaches work: integrating Java into the scripting language (which makes Java the third-party language) and integrated scripting languages into Java.

An important representative of the first category is Python in its Jython embodiment. This is the implementation of the standard Python language using Java, which makes it possible to import and use Java packages from within Python. This option may seem unnecessary from the Python point of view because CPython has a treasure trove of packages in its repository. However, if you need to integrate legacy Java applications, Jython is definitely an interesting option.

Rhinos to the Rescue

Java 1.6 saw the introduction of the standardized JSR-223 interface for accessing scripting languages in the javax.script package. The mechanism relies on a principle similar to integrating JDBC drivers. Listing 3 demonstrates the principle based on the nashorn ScriptEngine class developed in the course of the OpenJDK project. The name is a tribute to Mozilla's Rhino [5], the first JavaScript implementation in Java. Nashorn (German for rhinoceros) also implements a JavaScript runtime (ECMAScript Edition 5.1).

Listing 3: Java Runs a JavaScript File

01 import javax.script.*;
02 import java.io.*;
03
04 public class ExecScript {
05     public static void main(String[] args) throws Exception {
06         ScriptEngineManager manager = new ScriptEngineManager();
07         ScriptEngine engine = manager.getEngineByName("nashorn");
08         engine.eval(new FileReader(args[0]));
09     }
10 }

Using ScriptEngineManager (line 6), the code generates a ScriptEngine, which comes with a variety of eval() methods that run scripts from strings or files. The whole thing is dynamic; ScriptEngineManager implements an autodiscovery mechanism and serves up the engines. If the application finds the ScriptEngine for Python in the class path, it runs Python scripts without any programming overhead.

Online you will find an admittedly fairly old repository with ScriptEngines for all kinds of scripting languages [6]. A non-JSR-223-compliant variant for Jython itself is also available. Jython provides its interpreter as the org.python.util.PythonInterpreter class, and its exec() method executes the Python code that you pass in.

An Archipelago of Scripting Languages

There is hardly a scripting language that Java cannot integrate or for which a native implementation does not exist. Google finds at least five projects for Lua alone, including Luaj [7]. Along with CPython and Jython, you'll find JRuby [8], a Ruby implementation in Java. In contrast to the stubborn myths about Java's lack of speed, various tests demonstrate that the Java implementation of a scripting language actually beats the original implementation – typically in C/C++ – for speed.

Other languages use the JVM at run time; in other words, the compilers generate Java-compatible bytecode. Prominent examples include Clojure (Lisp) and Groovy. You can find a long list online [9].

Desktop Integration

Desktop integration is a further reason for mixing Java with a third-party language. The technique for doing so is similar to other application scenarios, and again it relies on JNI. For Linux desktops, there are the Java-Gnome [10] and Qt Jambi [11] projects. Because Qt itself is platform-independent, chances are the greatest here that integration with Windows will work.

In all, programming on the desktop turns out to be a borderline case for meaningful Java mixing. The desktop libraries are permanently on the move, and any connector can only play catch up with current events. However, because Java itself has a very good and platform-independent UI library today, the benefits of desktop use are restricted to the uniform look and feel – and this is typically not enough to outweigh the drawbacks.

More than Cold Coffee

Viewed geographically, Java may be an isolated island, but the programming language of the same name is cosmopolitan; it integrates more or less any popular third-party language worldwide. Additionally, it frequently can be integrated into those languages. The popularity of Java on mobile devices has given the language's versatility another boost. At the same time, you should not look to introduce code in other languages into your safe havens of Java without good reason. In particular, the portability of polyglot applications could be endangered, and this has always been one of the biggest arguments in favor of Java.