J/Link Training

Todd Gayley
Wolfram Research

NOTE: This training session was given for users with JLink 1.2 on their machines. Some features demonstrated do not exit in earlier versions of J/Link. Some discussion (such as setup instructions) is only true for J/Link 1.2 and later.

I.  Setup

Installation

J/Link can be downloaded from http://www.wolfram.com/solutions/mathlink/jlink. It also ships with Mathematica, as part of the MathLink Developer Kit.

J/Link 1.2 is installed simply by copying the JLink directory into one of the following locations:

ToFileName[{$TopDirectory, "AddOns", "Applications"}]

ToFileName[{$PreferencesDirectory, "AddOns", "Applications"}]

You do not have to separately install the J/Link native library (this was necessary in previous versions). J/Link can find its library automatically.

Mac OS 8/9 has slightly different instructions. See the J/Link docs.

Starting with Mathematica 4.2, J/Link will be installed along with Mathematica. Users need do nothing.

Class path

With the default installation, InstallJava[] will work.

If you want to run java tools (java, javac, etc.) from the command line, you will need to get JLink.jar on the Java class path. You can either set the CLASSPATH environment variable to include the full path to JLink.jar or you can use the -classpath option every time to point at JLink.jar

Details on setting the CLASSPATH environment variable for each platform are included in the J/Link installation instructions.

Here is an example command line that uses the -classpath option if you don't want to set CLASSPATH:

(Windows)
javac -classpath "c:\program files\wolfram research\mathematica\4.1\addons\applications\jlink\jlink.jar;%CLASSPATH%" MyClassThatUsesJLink.java

(UNIX)
javac -classpath /usr/local/mathematica/addons/applications/jlink/jlink.jar:$CLASSPATH MyClassThatUsesJLink.java

The machines we are working on today all have the CLASSPATH environment variable set to include JLink.jar:

Environment["CLASSPATH"]

Some Java development environments (e.g., JBuilder) do not respect the CLASSPATH environment variable. You will have to take steps specific to these products to ensure that your Java projects can access the classes in JLink.jar.

Java versions

Users should obtain a recent Java version. Java 1.3 is available on virtually all platforms.

For Windows, Linux, and Solaris, Java can be obtained from http://java.sun.com. Mac OS 8/9/X wil already have a Java runtime installed.

Starting with Mathematica 4.2, a Java runtime will be installed along with Mathematica on at least most platforms (Windows, Linux, Solaris).

Troubleshooting

If InstallJava[] fails, the best way to determine the problem is to run the following from a command line:

java com.wolfram.jlink.Install

If it works, you will see the J/Link copyright notice and a "Create Link" prompt. Click the Cancel button (on Windows) or hit Return (UNIX) to dismiss the prompt. Dismiss the subsequent "Connect To" prompt, and J/Link will report an error and quit.

Here are some possible failures and their fixes:

Problem: java executable cannot be found
Fix: Put java on your PATH, or use the CommandLine option to InstallJava to specify the full path:

SetOptions[InstallJava, CommandLine -> "/path/to/java"]

Problem: "NoClassDefFoundError" for com/wolfram/jlink.Install
Fix: (J/Link 1.1.x) Set up the CLASSPATH environment variable to point to JLink.jar. Better yet, get J/Link 1.2.
Fix: (J/Link 1.2) This is normal, and means simply that your CLASSPATH environment variable does not include JLink.jar. This will not cause a problem for InstallJava[] unless J/Link is not installed correctly.

Problem: Native library cannot be found.
Fix: (J/Link 1.1.x) Native library not installed in a correct location. Ignore discussion in installation instructions and get J/Link 1.2.

II.  Calling Java from Mathematica

Startup

<<JLink`

InstallJava[]

InstallJava should return a LinkObject.

We'll use this directory later:

$DevConfDir = CreateDirectory[ToFileName[{$TopDirectory, "AddOns", "Applications", "DevConf"}]]

Basics

Loading Classes

To use Java classes from Mathematica, they must first be loaded into Mathematica so that appropriate definitions can be automatically set up.

Classes are loaded automatically when you call JavaNew["classname"] to create a new object.

Sometimes you need to explicitly call LoadClass["classname"]. The most common reason for this is that you need to access a static method or field from the class, and the class has not previously been loaded.

LoadClass["java.lang.System"]
System`getProperty["os.arch"]

In case of a name conflict over a static method or field, you can always use the fully-qualified name:

java`lang`System`getProperty["os.arch"]

Calling Java

As an example, let's play with the BigInteger class.

bigint = JavaNew["java.math.BigInteger", 3^4^4]

Hmm, maybe we need some help about this class...

Constructors["java.math.BigInteger"]

bigint = JavaNew["java.math.BigInteger", ToString[3^4^4]]

bigint@flipBit[30]

Note that the above call returned an integer to Mathematica. Java types that have some useful "value" representation in Mathematica are converted to these values as they are sent to Mathematica. Other examples include strings and arrays (as Lists).

Methods[bigint, "flip*"]

Sometimes you want a Java object returned to Mathematica as an object reference, not as its raw value. You can use ByRef for this:

bigint@flipBit[30] // ByRef

The Val function is like the inverse of ByRef. It says to force an object reference to be converted to its value representation (if it has a meaningful one):

Val[%]

Writing and Deploying a Java Class

Let's create a Java class that we call from Mathematica.

We'll use Mathematica to create a Java file called Transpose.java and save it in the DevConf directory.

javaCode =
"
public class Transpose {

	public static int[][] tspose(int[][] a) {

		int[][] result = new int[a[1].length][a.length];
		
		for (int i = 0; i < a[1].length; i++)
			for (int j = 0; j < a.length; j++)
				result[i][j] = a[j][i];

		return result;
    }
}
";

f = OpenWrite[ToFileName[{$DevConfDir}, "Transpose.java"]];
WriteString[f, javaCode];
Close[f];

Now we compile it.

SetDirectory[$DevConfDir];
Run["javac Transpose.java"]

Let's load it into Mathematica and use it:

LoadClass["Transpose"]

AppendTo[$ExtraClassPath, $DevConfDir];

Now it works:

LoadClass["Transpose"]

Transpose`tspose[{{1,2,3},{4,5,6}}]

Using $ExtraClassPath is convenient while developing, but you don't want your users to need to take any extra steps like this to use your J/Link-based applications.

J/Link automatically finds Java classes in certain special locations. These locations are Java subdirectories of application directories (directories in AddOns\Applications or ExtraPackages\Applications).

Here is an example layout of an Mathematica application that uses J/Link. J/Link will automatically be able to find all Java classes and native libraries in this layout. The most typical use will be to bundle all your classes into a single jar file and put that jar file into a Java subdirectory of your main application directory.

<mathematica dir>/   (or the dir given by $PreferencesDirectory)
	AddOns/
		Applications/
			MyApp/
				MyApp.m
				Java/
					MyClasses.jar
					SomeOtherClass.class  (this class not in a Java package)
					com/
						acme/
							AcmeClass.class  (this class is com.acme.AcmeClass)
					Libraries/
						Windows/   ($SystemID for the given platform)
							MyNativeLib.dll

Managing Object References with JavaBlock

Whenever a Java object reference (a "JavaObject expression") is returned to Mathematica, a reference to this object is maintained on the Java side by J/Link. This ensures that the Java garbage collector does not destroy the object while it is in use in a Mathematica session.

No matter how many times the same object is sent to Mathematica, only one reference is ever maintained for it in Java.

When we are finished with an object in Mathematica, we need a way to tell J/Link to release the reference it holds on the object.

The function that does this is ReleaseObject. It tells Java that it no longer needs to keep an object around just for Mathematica's sake.

It can be quite tedious to keep track of all objects sent to Mathematica and call ReleaseObject on them, so J/Link provides the convenience function JavaBlock.

When a JavaBlock ends, all new objects sent to Mathematica during its execution will be released.

Many J/Link programs will never need to explicitly call ReleaseObject--JavaBlock will be enough for their needs.

At the end of the JavaBlock below, obj1 and obj2 will be released:

JavaBlock[
	...
	obj1 = JavaNew["some.class"];
	obj2 = obj1@someMethodThatReturnsAnObject[];
	...
]

New in J/Link 1.2: If a JavaBlock returns a Java object, it will not be released. This allows a useful programming idiom wherein a function does some work to create and return a Java object but wants all the objects created during the process to be treated as temporary and released.

At the end of the following JavaBlock, obj1 and obj2 will not be released:

JavaBlock[
	...
	obj1 = JavaNew["some.class"];
	...
	obj2 = obj1@someMethodThatReturnsAnObject[];
	...
	obj3 = obj1@someOtherMethodThatReturnsAnObject[];
	...
	obj1, KeepObjects->{obj2}
]

Good J/Link programs clean up all object references they create. You will see careful use of JavaBlock in all the J/Link example programs in the JLink\Examples\Part1 directory.

GUI Elements

A common use for J/Link is to build GUI elements for a Mathematica program. There are a number of tricks and techniques for this.

Let's build a simple window and implement some behavior for it in Mathematica.

frm = JavaNew["com.wolfram.jlink.MathFrame"];

frm@setSize[300, 200];

JavaShow[frm]

Make some room on your screen so the window is visible next to the notebook as you type.

LoadClass["java.awt.Color"];

frm@setBackground[Color`blue]

Now add a button.

btn = JavaNew["java.awt.Button"];
frm@add[btn];
btn@setBounds[100, 80, 90, 25];

btn@setLabel["Change Color"]

Now we want to wire up the button so that clicking it changes the color of the window.

Buttons fire ActionEvents when they are clicked. We want a class that implements the ActionListener interface via callbacks to Mathematica. J/Link comes with implementations of the AWT event listener classes that call back into Mathematica.

listener = JavaNew["com.wolfram.jlink.MathActionListener"]

The ActionListener interface has one method, actionPerformed(). Here is how we assign a Mathematica function to be called when the listener's actionPerformed() method is called:

listener@setHandler["actionPerformed", "buttonFunc"]

The J/Link user guide has a table that shows what arguments your Mathematica handler functions are passed for each method in the various "MathListener" classes. From this table, we see that ur handler function will get two arguments: the event itself and the integer action command. we are not interested in either:

buttonFunc[e_, _] :=
	JavaBlock[
		frm@setBackground[JavaNew["java.awt.Color",
			Random[Integer, {0,255}],
			Random[Integer, {0,255}],
			Random[Integer, {0,255}]]
		];
		ReleaseObject[e]
	]

btn@addActionListener[listener]

Now click the button. Did the color change? Click it a few more times. What's wrong?

ShareKernel and ShareFrontEnd

Take note of the proper way to use ShareKernel: always save the token it returns and use pass it to UnshareKernel when you are done.

tok = ShareKernel[]

UnshareKernel[tok]

ShareFrontEnd is an extension to ShareKernel that allows events initiated on the Java side to affect the notebook front end. One common use is to allow error messages generated during Java event callbacks to appear in the notebook.

Close the Color window.

Now lets package up our code as a "modal" window.

ColorWindow[] :=
   JavaBlock[
      Module[{frm, btn, listener},
         InstallJava[];
         frm = JavaNew["com.wolfram.jlink.MathFrame"];
         frm@setSize[300, 200];
         frm@setLayout[Null];
         LoadClass["java.awt.Color"];
         btn = JavaNew["java.awt.Button"];
         frm@add[btn];
         btn@setBounds[100, 80, 90, 25];
         btn@setLabel["Change Color"];
         listener = JavaNew["com.wolfram.jlink.MathActionListener"];
         listener@setHandler["actionPerformed", "buttonFunc"];
         btn@addActionListener[listener];
         buttonFunc[e_, _] :=
            JavaBlock[
               frm@setBackground[JavaNew["java.awt.Color",
                   Random[Integer, {0,255}],
                   Random[Integer, {0,255}],
                   Random[Integer, {0,255}]]
		       ];
		       ReleaseObject[e]
	        ];
         JavaShow[frm];
         frm@setModal[];
         DoModal[]
      ]
   ]

Now try it. Note that the ColorWindow function does not return until you close the window.

ColorWindow[]

Some Tips on Being a Polite J/Link Developer

Call InstallJava[] at the start of significant functions that require Java to be running.

Don't call UninstallJava[] in code you release to others. It is only for use in development (for example, if you want to reload a modified class you must shut down and restart Java).

Use JavaBlock and/or ReleaseObject to prevent unneeded Java object references from being kept around. But, don't bother with this during development--only as you prepare your code for use by others.

Always save tokens returned from ShareKernel and ShareFrontEnd and pass them to the respective UnshareXXX functions. Just because your app is done with sharing doesn't mean your user is.

Use the correct directory structure for your application so that users don't need to fiddle with their CLASSPATH to run your program.

III.  Calling Mathematica from Java

Overview of the J/Link API

There are two main link interfaces: MathLink and KernelLink.

The MathLink interface more or less corresponds to the C MathLink API, albeit simplified.

The KernelLink interface is the one that most programmers will use. It extends MathLink to include a higher-level interface appropriate for when the other side of the link is a Mathematica kernel. For example, KernelLink knows about the Mathematica packet loop, and it knows how to get graphics and typeset results.

Take a quick look at the JavaDocs.

A Simple Program

Select this cell, and select from the menu Edit/Save Selection As/Plain Text and save it as c:\SampleProgram.java.

import com.wolfram.jlink.*;

public class SampleProgram {

	public static void main(String[] argv) {

		KernelLink ml = null;

		try {
			ml = MathLinkFactory.createKernelLink(argv);
		} catch (MathLinkException e) {
			System.out.println("Fatal error opening link: " + e.getMessage());
			return;
		}

		try {
			// Get rid of the initial InputNamePacket the kernel will send
			// when it is launched.
			ml.discardAnswer();

			ml.evaluate("<<MyPackage.m");
			ml.discardAnswer();

			ml.evaluate("2+2");
			ml.waitForAnswer();
			int result = ml.getInteger();
			System.out.println("2 + 2 = " + result);

			// Here’s how to send the same input, but not as a string:
			ml.putFunction("EvaluatePacket", 1);
			ml.putFunction("Plus", 2);
			ml.put(3);
			ml.put("3");
			ml.endPacket();
			ml.waitForAnswer();
			result = ml.getInteger();
			System.out.println("3 + 3 = " + result);

			// If you want the result back as a string, use evaluateToInputForm or
			// evaluateToOutputForm. The second arg for either is the requested page
			// width for formatting the string. Pass 0 for PageWidth->Infinity.
			// These methods get the result in one step--no need to call waitForAnswer.
			String strResult = ml.evaluateToOutputForm("4+4", 0);
			System.out.println("4 + 4 = " + strResult);

		} catch (MathLinkException e) {
			System.out.println("MathLinkException occurred: " + e.getMessage());
		} finally {
			ml.close();
		}
	}
}

Now, from a DOS command line, execute:

javac SampleProgram.java

This works because JLink.jar has already been added to your CLASSPATH.

Now from the command line, run it:

java SampleProgram -linkmode launch -linkname "c:\program files\wolfram research\mathematica\4.1\mathkernel"

Where's the Packet Loop?

If you have written a MathLink program using the C API, you may be wondering why the "packet loop" of nextPacket()/newPacket() does not appear in the program.

J/Link hides the packet loop inside waitForAnswer(), discardAnswer(), and the "evaluateTo" functions.

Unless you are absolutely sure you need to call nextPacket() yourself, don't do it. Leave the packet loop inside the internals of J/Link.

PacketPrinter for Debugging

OK, great, you're happy to not have to deal with the packet loop manually. But what if you want more than just the "answer" to a computation? You might want to see Mathematica messages or Print output that was generated during the computation.

J/Link lets you "hook into" the packet loop to observe the flow of packets that arrive. It does this via the PacketListener interface.

J/Link comes with one implementation of PacketListener called PacketPrinter. It simply prints the contents of each packet to a stream of your choice (defaults to System.out).

Using a PacketPrinter is a trivial yet powerful and often overlooked aid in debugging.

To see it in action, let's break our program. In one of the lines that reads ml.put(3), change it to ml.put("3"). Then recompile and run the program.

Now add the following line to the program, right before the ml.putFunction("EvaluatePacket", 1):

ml.addPacketListener(new PacketPrinter());

Now recompile and run again.

Graphics

Graphics functions

J/Link has the following functions for getting graphics output in Java programs:

byte[] evaluateToImage(String s, int width, int height);
byte[] evaluateToImage(Expr e, int width, int height);

byte[] evaluateToImage(String s, int width, int height, int dpi, boolean useFE);
byte[] evaluateToImage(Expr e, int width, int height, int dpi, boolean useFE);

byte[] evaluateToTypeset(String s, int width, boolean useStdForm);
byte[] evaluateToTypeset(Expr e, int width, boolean useStdForm);

Here is an example of constructing a Java Image object from the output:

byte[] gifData = ml.evaluateToImage("Plot[x,{x,0,1}]", 0, 0, 0, false);
java.awt.Image im = java.awt.Toolkit.getDefaultToolkit().createImage(gifData);

MathCanvas

J/Link has an even higher-level way to get Mathematica graphics and typeset output into your Java programs: the MathCanvas class.

Select this cell, and select from the menu Edit/Save Selection As/Plain Text and save it as c:\GraphicsApp.java.

import com.wolfram.jlink.*;
import java.awt.*;
import java.awt.event.*;

public class GraphicsApp extends Frame {
    
	static GraphicsApp app;
	static KernelLink ml;
	
	MathCanvas mathCanvas;
	TextArea inputTextArea;
	Button evalButton;
	Checkbox useFEButton;
	Checkbox graphicsButton;
	Checkbox typesetButton;

	public static void main(String[] argv) {

		try {
			String[] mlArgs = {"-linkmode", "launch", "-linkname", argv[0]};
			ml = MathLinkFactory.createKernelLink(mlArgs);
			ml.discardAnswer();
		} catch (MathLinkException e) {
			System.out.println("An error occurred connecting to the kernel.");
			if (ml != null)
				ml.close();
			return;
		}
		app = new GraphicsApp();
	}
    
	public GraphicsApp() {
		
		setLayout(null);
		setTitle("Graphics App");
		mathCanvas = new MathCanvas(ml);
		add(mathCanvas);
		mathCanvas.setBackground(Color.white);
		inputTextArea = new TextArea("", 2, 40, TextArea.SCROLLBARS_VERTICAL_ONLY);
		add(inputTextArea);
		evalButton = new Button("Evaluate");
		add(evalButton);
		evalButton.addActionListener(new BnAdptr());
		useFEButton = new Checkbox("Use front end", false);
		CheckboxGroup cg = new CheckboxGroup();
		graphicsButton = new Checkbox("Show graphics output", true, cg);
		typesetButton = new Checkbox("Show typeset result", false, cg);
		add(useFEButton);
		add(graphicsButton);
		add(typesetButton);
		
		setSize(300, 400);
		setLocation(100,100);
		mathCanvas.setBounds(10, 25, 280, 240);
		inputTextArea.setBounds(10, 270, 210, 60);
		evalButton.setBounds(230, 290, 60, 30);
		graphicsButton.setBounds(20, 340, 160, 20);
		typesetButton.setBounds(20, 365, 160, 20);
		useFEButton.setBounds(180, 340, 120, 20);
		
		addWindowListener(new WnAdptr());
		setBackground(Color.lightGray);
		setResizable(false);
		
		setVisible(true);
		toFront();
	}
    
	class BnAdptr implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			mathCanvas.setImageType(graphicsButton.getState() ? MathCanvas.GRAPHICS : MathCanvas.TYPESET);
			mathCanvas.setUsesFE(useFEButton.getState());
			mathCanvas.setMathCommand(inputTextArea.getText());
		}
	}
	
	class WnAdptr extends WindowAdapter {
		public void windowClosing(WindowEvent event) {
			if (ml != null) {
				ml.evaluateToInputForm("CloseFrontEnd[]", 0);
				ml.close();
			}
			dispose();
			System.exit(0);
		}
	}
}

Now, compile and run it from the command line with these lines:

javac GraphicsApp.java
java GraphicsApp "c:\program files\wolfram research\mathematica\4.1\mathkernel"

Expr and Loopback Links

In SampleProgram.java, we saw an example of "manually" putting a computation on the link, starting with ml.putFunction("EvaluatePacket", 1). This is particularly useful if you don't want to convert your input into a string of Mathematica InputForm. "Stringifying" the input would be quite inconvenient and inefficient if you wanted to send a 2-dimensional array of 1,000,000 numbers.

But what if you want to take advantage of the high-level functions like evaluateToImage()? This is where the Expr class comes in:

byte[] evaluateToImage(String s, int width, int height);
byte[] evaluateToImage(Expr e, int width, int height);

The Expr class provides a Java representation of an arbitrary Mathematica expression. Although Expr now (in J/Link 1.2) has a reasonable set of constructors, the most common way of creating one is to read one off a link using getExpr().

Therefore, to create our input as an Expr, we build it on a loopback link and read the Expr off the link.

Here is the easiest way to get a ListPlot of a very large data array:

LoopbackLink loop = MathLinkFactory.createLoopbackLink();
loop.putFunction("ListPlot", 1);
loop.put(myLargeDataArray);
Expr e = loop.getExpr();
byte[] gifData = ml.evaluateToImage(e, 300, 300);
loop.close();
e.dispose();

MathSessionPane

J/Link 1.2 includes a new component called MathSessionPane that implements a complete In/Out window. It is a bit like the kernel's terminal interface, except that it has many more features. It is intended for developers who want a quick way to embed a direct interface to the Mathematica kernel into their programs. It could also be used as the foundation for a reasonably sophisticated front end to Mathematica.

MathSessionPane is very customizable and extensible via properties. We won't have time to examine many of them.

Select this cell, and select from the menu Edit/Save Selection As/Plain Text and save it as c:\MathSessionPaneExample.java.

import com.wolfram.jlink.MathLinkException;
import com.wolfram.jlink.ui.*;
import java.awt.*;
import java.awt.event.*;

public class MathSessionPaneExample {    

    public static void main(String[] argv) {
 		
 		final MathSessionPane msp = new MathSessionPane();
 
 		// Create the frame window to hold the pane.
 		Frame frm = new Frame();
 		frm.setSize(500, 500);
 		frm.setLayout(new BorderLayout());
 		frm.add(msp, "CENTER");
		frm.add(msp);
 		frm.doLayout();
 		frm.setVisible(true);
 		
 		// This causes the link to be closed when the Frame is closed.
 		frm.addWindowListener(new WindowAdapter() {
 			public void windowClosing(WindowEvent e) {
 				msp.closeLink();
 				System.exit(0);
 			}
 		});
 		
 		// Modify this for your setup.
 		msp.setLinkArguments("-linkmode launch -linkname d:/math412/mathkernel");
 		try {
 			msp.connect();
 		} catch (MathLinkException e) {
 			e.printStackTrace();
 		}
 	}
}

Now compile and run it from the command line with these lines:

javac MathSessionPaneExample.java
java MathSessionPaneExample