JDEE Universal Communication Interface (JUCI)

JUCI (pronounced `juicy') is YABA (Yet Another Bloody Anagram) for the JDEE Universal Communication Interface. The intent of JUCI is to provide a standard programming convention and infrastructure for managing and integrating communication between Elisp and Java programs through the Beanshell.

Contents

Overview

JUCI provides two-way communication between Elisp code running in Emacs and Java code running in the Beanshell. This means that not only can Java code be executed in a uniform manner by Elisp code, but Java code has the ability to call back to Elisp code, retrieving user input, querying the current buffer position or filename, or any other conceivable information. This is achieved by spawning a separate, parallel thread inside the JVM running the Beanshell to execute Java code so that if that code wishes to call back to Elisp, the foreground Beanshell thread can still be free to receive the result of that evaluation through further script statement evaluation. The two threads work together through the JUCI Connection object (maintained on the Java side) which is shared between them and represents a sort of "interprocess communication session."

Using JUCI

Calling Java from Elisp is straightforward; you create a Java interface containing the methods you wish to call from Elisp and an implementation of that interface that has a default constructor:

package my.util;

public interface Helper {
  Object doSomething(Object arg);
}

public class HelperImpl implements Helper {
  public HelperImpl() {}

  public Object doSomething(Object arg) {
    // ...
  }
}

[Aside Note: Creating the interface/implementation split is a necessary step because of JUCI's use of the java.lang.reflect.Proxy mechanism to interpose/decorate around the underlying Java method being invoked. In this case, the connection proxy decorates the implementation by queuing the method for invocation on the background thread. A notable side-effect of this is that Java code that is executed on the Beanshell's primary thread cannot use JUCI to call Elisp; the Java code needs to be initially invoked through JUCI for that to happen. (However, the LispWriter class is still available to use standalone to generate lisp forms in that case.)]

On the Elisp side, in order to call this Java class, all you would need to do is invoke a JUCI helper function jde-juci-invoke-java like this:

(defun my-util-helper-do-something (arg)
  (jde-juci-invoke-java "my.util.HelperImpl" "doSomething" arg))

For Java code that calls Elisp, you declare just an interface. On the Elisp side, you implement that interface by creating a function with a name following certain conventions (see below) and a matching argument list:

package my.util;

public interface Prompt {
  String getUserInput(String prompt);
}

;; Elisp implementation of above interface
(defun my-util-prompt-get-user-input (prompt)
  (read-from-minibuffer prompt))

// Java client code that invokes Elisp implementation

Prompt prompt = (Prompt) jde.juci.ConnectionFactory.getConnection(Prompt.class);
String result = promt.getUserInput("Your name: ");

This will cause Emacs to enter the minibuffer (prompting with the string "Your name: "), capture the user-entered string, and return it to the Java code.

JUCI naming convention

In order to connect Elisp code with the Java interface method it implements, a naming convention has been established. The Elisp function that is called when you invoke a Java interface (in the Java-to-Elisp direction) will be determined by applying the following algorithm to the fully qualified class name and method

For example, the Java interface method jde.foo.Bar.frobnicate() would translate to jde-foo-bar-frobnicate in Elisp.

The naming convention is also in effect in the other direction, except it is only suggested, not enforced (since the mapping using the above algorithm would be one Elisp name to many Java names and because the jde-juci-invoke-java function takes a fully-qualified class name and a method name as arguments).

Conversion of objects between Elisp and Java

One of the key duties of JUCI is to faithfully convert Elisp objects to a Beanshell script form of its closest Java equivalent, and vice versa. The following table indicates conversion equivalents implemented by JUCI:

Elisp to Java conversion (via jde-juci-bshify-object function)
Elisp object Java object
t true (Boolean.TRUE)
nil false (Boolean.FALSE)
'null null
number implicit conversion to java.lang.Number subclasses (done by the Beanshell)
string java.lang.String
symbol jde.juci.Symbol
dotted-pair jde.juci.Cons
alist (sequence of dotted-pairs) java.util.Map †
any sequence java.util.List

† At the moment, half of this conversion is actually done inside the ConnectionImpl class; the Elisp conversion layer converts to a List of Cons objects, and then ConnectionImpl detects this and finishes the job. The main reason for this is that there isn't an easy way to construct a map inline (no analogue to java.util.Arrays.asList).

Java to Elisp conversion (via jde.juci.LispWriter class)
Java object Elisp object
true (Boolean.TRUE) t
false (Boolean.FALSE) nil
null nil
java.lang.Number implicit conversion to Elisp number (done by the Emacs eval function)
java.lang.String string
jde.juci.Symbol symbol
jde.juci.Cons dotted-pair
java.util.Map alist (sequence of dotted-pairs)
java.util.Collection list (ordered by the collection's iterator)
(TODO) java.lang.Object[] list

Components of JUCI

For those who wish to delve further into the implementation details of JUCI, the following is a road map of where in the source code to look for aspects of JUCI's functionality:

Debugging

Debugging problems in the JUCI layer is tricky for several reasons; largely because the flow of execution switches from Emacs and its Elisp engine to the Beanshell scripting engine to Java code. At this point, you're pretty much limited to logging statements. But since standard output is one of the communication channels, Java code cannot rely on trusty System.out.println. For this, there is a Logger class that writes to a file that can be invoked from the ConnectionImpl. To wire up the file that the logger dumps to, set the variable jde-juci-logger-filename on the Elisp side with the log filename. When a JUCI connection is created, if this variable is set, the setLoggerFilename method on the connection will be called and this enables the logger.

On the Elisp side, every statement sent to the Beanshell for execution is copied to the *jde-log* buffer.

Some attempt is made to preserve errors/exceptions and send them back across the connection boundary. Usually an error/exception will eventually unwind and result in a RuntimeException being thrown from the last statement evaluated in the Beanshell (which bsh-eval dutifully replicates to the *Messages*/*Message-Log* buffer).

TODO

License

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Last modified: Sun Feb 23 23:27:46 Central Standard Time 2003