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.
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."
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.
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).
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 |
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:
jde.juci
package) Connection
-- the interface that represents a
JUCI session. Constructed by the
ConnectionFactory
, it also is a proxy for the
class(es) provided to the factory.ConnectionFactory
-- factory for
Connection
objects.ConnectionImpl
-- under-the-hood
implementation of the connection interface that serves as
traffic cop for the pair of threads that send data back and
forth between Elisp and Java.LispWriter
-- object responsible for converting Java objects to lisp
forms on standard output.Symbol/Cons/Quoted
-- compatibility objects
that can be used by Java code to properly construct more
complicated Elisp forms. The Symbol object ensures that a
string will be translated without double quotes. The Cons
object can be used to construct a dotted-pair form such as
those found in typical Elisp alists (e.g., (a
. b)
). The Quoted object is the equivalent of the
single-quote (') or the (quote ...)
form.jde-juci.el
Elisp package) jde-juci-invoke-java, jde-juci-invoke-script
-- function used to invoke a Java object or Beanshell script
through JUCI.jde-juci-invoke-elisp
-- function used by the
Java side of the JUCI infrastructure to execute an Elisp
function and return its results back to Java.jde-juci-bshify-object
-- function used to
convert Elisp objects to Beanshell script versions of the
equivalent Java objects. 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).
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