1.2.17 Implementing a Java Interface with Mathematica Code
We have seen how J/Link lets you write programs that use existing Java classes. We have also seen how you can wire up the behavior of a Java user interface via callbacks to Mathematica via the "MathListener" classes. You can think of any of these MathListener classes, such as MathActionListener, as a class that "proxies" its behavior to arbitrary userdefined Mathematica code. It is as if we have a Java class that has its implementation written in Mathematica. This functionality is extremely useful because it greatly extends the set of programs we can write purely in Mathematica, without resorting to writing our own Java classes.
Implementing a Java interface entriely in Mathematica.
It would be nice to be able to take this behavior and generalize it, so that you could take any Java interface and implement its methods via callbacks to Mathematica functions, and do it all without having to write any Java code. The ImplementJavaInterface function, new in J/Link 2.0, lets you do precisely that. This function is easier to understand with a concrete example. Say you are writing a Mathematica program that uses J/Link to display a Java window with a Swing menu, and you want to script the behavior of the menu in Mathematica. The Swing JMenu class fires events to registered MenuListeners, so what you need is a class that implements MenuListener by calling into Mathematica. A quick glance at the section on MathListeners reveals that J/Link does not provide a MathMenuListener class for you. You could choose to write your own implementation of such a class, and in fact this would be very easy, even trivial, since you would make it a subclass of MathListener and inherit virtually all the functionality you would need. For the sake of this discussion, assume that you choose not to do that, perhaps because you don't know Java or you don't want to deal with all the extra steps required for that solution. Instead, you can use ImplementJavaInterface to create such a Java class with a single line of Mathematica code:
mathMenuListener = ImplementJavaInterface["javax.swing.event.MenuListener", {"menuSelected" > "menuSelectedFunc", "menuCanceled" > "menuCanceledFunc", "menuDeselected" > "menuDeselectedFunc"} ]; myMenu@addMenuListener[mathMenuListener];
...
(* Later, define the three Mathematica eventhandler functions: *) menuSelectedFunc[menuEvent_] := ...
menuCanceledFunc[menuEvent_] := ...
menuDeselectedFunc[menuEvent_] := ...
The first argument to ImplementJavaInterface is the Java interface or list of interfaces you want to implement. The second argument is a list of rules that associate the name of a Java method from one of the interfaces with the name of a Mathematica function to call to implement that method. The Mathematica function will be called with the same arguments that the Java method takes. What ImplementJavaInterface returns is a Java object of a newlycreated class that implements the named interface(s). You use it just like any JavaObject obtained by calling JavaNew or through any other means. It is just as if you had written your own Java class that implemented the named interface by calling the associated Mathematica functions, and then called JavaNew to create an instance of that class.
It is not necessary to associate every method in the interface with a Mathematica function. Any Java methods you leave out of your list of mappings will be given a default Java implementation that returns null. If this is not an appropriate return value for the method (e.g., if the method returns an int) and the method gets called at some point an exception will be thrown. Generally, this exception will propagate to the top of the Java call stack and be ignored, but it is recommended that you implement all the methods in the Java interface.
The ImplementJavaInterface function makes use of the "dynamic proxy" capability introduced in Java 1.3. It will not work in Java versions earlier than 1.3. All of the Java runtimes bundled with Mathematica 4.2 and later are at version 1.3 or later. If you have Mathematica 4.0 or 4.1, the ImplementJavaInterface function is another reason to make sure you have an uptodate Java runtime for your system.
At first glance, the ImplementJavaInterface function might seem to give us the capability to write arbitrary Java classes in the Mathematica language, and to some extent that is true. One important thing you cannot do is extend, or subclass, an existing Java class. You also cannot add methods that do not exist in the interface you are implementing. Eventhandler classes are a good example of the type of classes for which this facility is useful. You might think that the MathListener classes are rendered obsolete by ImplementJavaInterface, and it is true that their functionality can be duplicated with it. The MathListener classes are still useful for Java versions earlier than 1.3, but most importantly they are useful for writing pure Java programs that call Mathematica. Using a class implemented in Mathematica via ImplementJavaInterface in a Java program that calls Mathematica would be possible, but quite cumbersome. If you want a dualpurpose class that is as easy to use from Mathematica as from Java, you should write your own subclass of MathListener. One poor reason for choosing to use ImplementJavaInterface instead of writing a custom Java class is that you are worried about complicating your application by requiring it to include its own Java classes in addition to Mathematica code. As explained in the section on deploying applications that use J/Link, it is extremely easy to include supporting Java classes in your application. Your users will not require any extra installation steps nor will they need to modify the Java class path.
