| Allegro CL version 8.1 Minimally revised from 8.0. 8.0 version |
This document contains the following sections:
1.0 IntroductionStarting with the patch release of May 2006, the Jlinker module is identical in ACL 7.0 and ACL 8.0.
The symbols in the jLinker module are exported from the package
:javatools.jlinker
. The package also has
nickname :net.jlinker
. In the next release of
Allegro CL, the name and nickname will change places, and in the
following release, the name :javatools.jlinker
will
be deprecated.
The purpose of this tool is to automate the interfacing of Lisp programs to Java class libraries. Jlinker supports a socket interface that allows the Lisp and Java parts of an application to run in separate process, and even in separate hosts. Jlinker also supports a native interface (new for 8.0) that allows the Lisp and Java parts of an application to share the address space of a single process. Both interfaces are accessed through the same API and in many cases, the choice of interface does not affect the application program style.
jLinker allows dynamic, unpremeditated access to the public methods, constructors, and members of Java classes from the Lisp runtime environment.
The end result is that the Lisp application may call Java methods as if they were Lisp functions. The documentation of the Java class is all that the Lisp programmer needs to know to use the Java library effectively. For example, the Java statements
java.awt.Canvas canvas = new java.awt.Canvas(); canvas.setSize( new java.awt.Dimension(12, 17) );
have the Lisp equivalent
(setf canvas (jnew "java.awt.Canvas")) (jcall "setSize" canvas (jnew "java.awt.Dimension" 12 17))
Remote objects are retained as long as a reference exists in the calling environment. When a Lisp reference to a remote Java object is discarded and garbage collected, a reference to the Java object is eventually eliminated. The retention of the Java object is then controlled by Java rules.
To improve the efficiency of the interface, we allow values returned by methods to be ignored or copied. This prevents the construction of a remote object on the Java side. Ignored values are applicable to values that are already known or irrelevant to the Lisp application. Copied objects are applicable when the Java object is used only for the values of its fields and not for any method invocations.
Calls from Java to Lisp are implemented by the LispCall class in Java. To facilitate some commonly used callbacks from Java to Lisp we provide some adapter and listener classes that send AWT event data to a dispatcher on the Lisp side. This framework has been suitable for all the callbacks used in the java.awt library.
We have tested the concept with several Lisp applications that use the java.awt library for the gui component of the application. The performance of the gui is comparable to a native Java application in most cases. We have demonstrated portability by running the same application on Microsoft Windows NT and Sun Solaris.
Symbols naming Lisp operators and variables associated with jLinker
are in the javatools.jlinker
package. You may want
to use this package so symbols need not be qualified with
javatools.jlinker:
. Do this by evaluating
(use-package :javatools.jlinker)
. See use-package.
The jLinker module is loaded by evaluating:
(require :jlinker) ;;; You may load additional features ;;; (see Section 5.0 Packaging Lisp applications as Java beans and servlets) ;;; by evaluating: (require :jlinkent)
Note that only public methods, constructors, and members of public Java class may be accessed from Lisp. This constraint is implicit throughout this document whenever we mention a Java class, method, constructor, or member.
Once a Jlinker interface has been initialized, Java constructors and methods are called by name.
All the following examples are shown in a
:case-sensitive-lower
Allegro CL lisp notation. In a
standard (:case-insensitive-upper
Allegro CL) all Java
names would need to be enclosed in string quotes.
The form
(jconstructor 'java.util.StringTokenizer 'java.lang.String 'java.lang.String)
returns a reference to a constructor, and the form
(jcall (jconstructor 'java.util.StringTokenizer 'java.lang.String 'java.lang.String) "ABC DEF GHI " " ")
returns a reference to an instance created by the constructor. These references are ordinary Lisp objects that may be bound to variables and stored in data structures.
(jcall (jmethod 'java.util.StringTokenizer 'countTokens) x)
The operator lookup functions maintain a cache so that only the first mention of a class, constructor, method or field requires a remote call.
We provide two calling models that may be used separately or simultaneously at the discretion of the programmer.
In the funcall model, Java classes and methods are referenced by name. This is a somewhat verbose style but is convenient for quick prototyping since all Java classes and methods are immediately available without any additional work on the Java or the Lisp side. For example, the following two statements
(setq x (jnew (jconstructor "java.util.StringTokenizer" "java.lang.String" "java.lang.String") "a b c " " ")) (jcall (jmethod "java.util.StringTokenizer" "nextToken") x)
create an instance of the Java
java.util.StringTokenizer
class and call the
nextToken
method on the the new instance. In the
funcall model, a method or constructor may be specified with an
incomplete signature such as
(jconstructor "java.util.StringTokenizer" 2)
This notation specifies a constructor with two arguments. If there is only one constructor with two arguments in the Java class, then we return the constructor. Otherwise, we signal a continuable error where the desired constructor may be selected from a list of choices. A similar short-cut is possible in a call such as
(jcall "nextToken" x)
Here we are calling the (only) nextToken
method
with zero arguments in the class of the object x. If several methods
were available, then a continuable error would again be signalled.
Incomplete signatures are very convenient during development but should be avoided in final applications since searching for methods is a slow process that may require multiple round-trips between Lisp and Java.
In the class model, the user defines Lisp classes that
correspond to Java classes and Lisp functions that correspond to Java
constructors and Java methods. The Lisp functions that correspond to
Java class methods are generic functions specialized on Lisp classes
that correspond to Java classes. The Lisp functions that correspond to
Java static methods are ordinary functions. To translate the
preceding java.util.StringTokenizer
example to the
class model, we need to make some definitions:
(def-java-class (tokenizer "java.util.StringTokenizer") () () () ()) (def-java-constructor tokenizer (tokenizer "java.lang.String" "java.lang.String")) (def-java-method (next-token "nextToken") (tokenizer))
When we use these definitions, the code is more compact and Lisp-like:
(setq x (tokenizer "a b c " " ")) (next-token x)
When Lisp values are passed as arguments in a call to Java, the Lisp values are converted to the Java types specified in the method signature. The value returned by a call to Java is converted to a Lisp value following the rules in the table below.
In Java, values are automatically converted by calling the appropriate method of the LispCall class.
Declared Java Type | Allowed Lisp Type | Actual Java type (or value) | Conversion Note |
boolean | null | (false) | |
boolean | any non-nil | (true) | |
char | character | char | |
byte | integer | byte | truncate to 7 bits + sign |
short | integer | short | truncate to 15 bits + sign |
int | integer | int | truncate to 31 bits + sign |
long | integer | long | truncate to 63 bits + sign |
float | number | float | |
double | number | double | |
java.lang.String | string | java.lang.String | |
byte[] | sequence | byte[] | truncate to 7 bits + sign |
short[] | sequence | short[] | truncate to 15 bits + sign |
int[] | sequence | int[] | truncate to 31 bits + sign |
float[] | sequence | float[] | |
double[] | sequence | double[] | |
String[] | sequence | String[] | |
reference type | jwrapper | Java reference in wrapper | |
reference type | null | (null) | |
reference type | any | JLWrapper |
Java Type | Lisp Type (or Value) |
boolean | (t or nil) |
byte | integer |
short | integer |
int | integer |
long | integer |
char | character |
String | string |
float | double-float |
double | double-float |
byte[] | (array (signed-byte 8) (*)) |
short[] | (array (signed-byte 16) (*)) |
int[] | (array (signed-byte 32) (*)) |
float[] | (array double-float (*)) |
double | (array double-float (*)) |
String[] | (array t) |
null | null |
JLWrapper | Lisp type in wrapper |
reference type | jwrapper |
The conversions in the above tables are new for Allegro CL 8.0. In earlier versions of Jlinker, the types in the method signature were not used during argument conversion and the function make-immediate-object was required to coerce many Lisp types to the correct Java type. Existing calls to make-immediate-object will still produce the correct result but in all cases these calls are now redundant.
There are two distinctly different implementations of the Jlinker interface.
In the socket implementation, the Lisp and Java parts of the application run in separate processes and may even run on separate hosts. A significant feature is that the address spaces of both parts are separate and protected. Each side of the application may be stopped and restarted without affecting the other. A disadvantage is that the speed of interactions is limited by the data transfer rate of sockets and may be subjected to indefinite network delays.
In the native implementation, the Lisp and Java parts of the application run in the same process and share the same address space. A significant feature is that calls between Lisp and Java run at the speed of foreign calls. A possible disadvantage may be that the address space may be reduced for both parts of the application. Anoter potential disadvantage is that a serious error in one part may cause the other part to crash as well.
We use the following meta notations for certain arguments:
In the cases where a compiler macro exists for a jLinker function, the compiler macro examines class-ref and method-ref arguments. If the arguments are compile-time constants which evaluate to a string or symbols, the compile-time values of these arguments are used to build the pre-load expressions.
The macro def-java-class can be used to define a Lisp class which corresponds to a Java class.
The macro def-java-constructor allows defining constructor functions to create instances of the classes defined with def-java-class. The macro def-java-method can be used to define methods. def-java-static defines static methods.
All the following classes and methods are defined in Java package
com.franz.jlinker
and supplied in the
file jlinker.jar.
The recommended Java interface to jLinker is implemented in Java classes LispCall and LispConnector documented in JavaDoc files supplied with the documentation (linked to just below). These two classes provide all the API that a Java program needs to call Lisp or to allow Lisp to call Java. The remainder of this section describes additional Java classes and methods that implement similar but deprecated interfaces retained for compatibility with older versions of ACL.
LispCall Java class documentation: the JavaDoc pages are automatically generated and do not have links to this documentation. Click here to open the LispCall documentation in a new window, and here to (usually) open in in this window.
LispConnector Java class documentation: the JavaDoc pages are automatically generated and do not have links to this documentation. Click here to open the LispConnector documentation in a new window, and here to (usually) open in in this window.
The JavaDoc index: click here to see the index of the available JavaDocs in a new window and here in this window (usually). Only the LispCall and LispConnector classes have JavaDoc documentation.
In earlier releases, there were additional classes and methods which
are now replaced by LispCall and LispConnector
classes. Use of these older classes and methods is deprecated. Their
descriptions have been moved to
Appendix A Deprecated Dynamic Linkage Reference. They are retained for
compatibility with existing applications. They may only be used with
the socket implementation of the jlinker interface. (The only
exceptions are the subclasses of
JavaLinkDist.JLinkerException
.) All the
equivalents of the deprecated classes and methods in the
LispCall and LispConnector classes allow full
portability between socket and native modes.
Package: com.franz.jlinker Class: JavaLinkDist.JLinkerException
This is an abstract superclass of all the jLinker exceptions.
Package: com.franz.jlinker Class: JavaLinkDist.InvokeException
This exception is thrown when some unexpected situation occurs in a call
to invokeInLispEx. getMessage()
returns a string of the form
"Nothing returned from Lisp" "Unexpected value: ..." "Unexpected result: ..."
when such an exception is signaled.
Package: com.franz.jlinker Class: JavaLinkDist.LispException
This exception is thrown when a Lisp error occurs in a call to
invokeInLispEx. getMessage()
returns a string
description of the error when such an exception is signaled.
Package: com.franz.jlinker Class: JavaLinkDist Method: lispError public static String lispError( JavaLinkDist.LispException x );
This method returns a remote reference to the Lisp error that caused
the JavaLinkDist.LispException exception. stringValue(err,
0)
returns a string containing the Lisp type of the error.
stringValue(err, 1)
returns a string containing the
~A representation of the Lisp error.
Package: com.franz.jlinker Class: JavaLinkDist.LispThrow
This exception is thrown when a Lisp throw terminated the Lisp call initiated by a call to invokeInLispEx.
The functions described in this section are used to setup and query the interface between Lisp and Java.
The functions and variables are:
*jlinker-init*
: this variable
specifies a default set of arguments to the jlinker-init function. The
initial value in the jlinker module is
(:start-java)
.
*jlinker-connection*
:
This anchor variable defines all the parameters for one complete connection
between Lisp and Java.
*jlinker-verbose*
:
The value of this variable is the default value for the :verbose keyword argument to
jlinker-init and
jlinker-end.
When true Lisp emits many status messages while connecting.
*jlinker-debug*
: The value of
this variable is the default value for the :debug keyword argument to
jlinker-init.
*jlinker-error-p*
: The value of
this variable is the default value for the :error-p keyword argument
to jlinker-init
and jlinker-end.
*jlinker-set-accessible*
: The
value of this special variable determines the jLinker behavior when
Java throws java.lang.IllegalAccessException
during
a method call.
*jlinker-run-java*
:
*file-type-comparator*
:
The value of this variable should be a function name or function object
of the function used to compare file types.
*jlinker-deprecated-warnings*
: if
this variable is set to a non-nil
value, then
the compiler will signal warnings if deprecated operators are
encountered. In any case, the compiler collects the operators of
deprecated forms in a list stored in this variable. The initial value
is nil
.
On MS Windows, jLinker is able to locate the Java executable and the required jar files by examining the Windows registry. Therefore, in most installations, it is not necessary to configure jLinker.
On Unix, Linux or MacOSX systems, we have not discovered a general method for discovering the location of the Java executable and libraries. Therefore some configuration is necessary. The sample file jlinker/jl-config.cl is a template that can be customized to set the jLinker configuration variables in several different situations.
*jlinker-java-home*
: this
variable must be set to the location of the Java installation. On MS
Windows, the keyword :find
specifies a Registry
search.
*jni-library*
: this variable is
used only when the native Java interface is used. The value of this
variable must be the pathname string of the Java JNI shared library
(libjni.so, libjni.dll, libjvm.dylib ...). If the value is a relative
path string, then it is merged with the value of *jlinker-java-home*
. On MS
Windows, the keyword :find
specifies a Registry
search.
Many Java classes customize their behavior by allowing the programmer to extend them with custom implementations of selected methods. The java.awt package makes extensive use of this facility to handle the events associated with the use of a GUI.
In a distributed computing environment, the question arises of where the custom implementations of the methods should be executed. There is a range of answers to this question and some of the possibilities are discussed in the following sections.
If the custom behavior of an extended method does not require any data from the Lisp side of the application, the method can be implemented in a pure Java extension of the class in question. The extended method may be linked to the application from Lisp.
----- Java ---------- public class MyWindowAdapter extends WindowAdapter { public void windowClosing(WindowEvent e) { e.getWindow().dispose(); }; ----- Lisp ---------- (jcall "addWindowListener" frame (jnew "MyWindowAdapter"))
The Java method may also call back to Lisp with
LispCall
methods.
When callback methods follow a common pattern, it may be possible to implement a general function that passes enough information from Java to Lisp through a common interface.
In the case of java.awt events, this is a very reasonable approach, and we have subclassed many of the event handlers to transmit event information to Lisp in a common form where it is dispatched by Lisp functions.
(jcall (jmethod "com.franz.jlinker.JLWindowAdapter" "addTo") frame) (jregister-handler frame :windowClosing #'(lambda (data frame &rest x) (jcall "dispose" frame)))
This approach can be extended or modified to handle a wide range of callback situations.
class com.franz.jlinker.JLActionListener implements ActionListener Methods: addTo(java.awt.Button) addTo(java.awt.List) addTo(java.awt.MenuItem) addTo(java.awt.TextField) Handler arguments: object is argument to addTo event= :actionPerformed longs= { event.getModifiers() } strings= { event.paramString(), event.getActionCommand() }
class com.franz.jlinker.JLComponentAdapter extends ComponentAdapter Methods: addTo(java.awt.Component) Handler arguments: object is argument to addTo event= :componentResized :componentMoved :componentShown :componentHidden longs= { } strings= { event.paramString() }
class com.franz.jlinker.JLItemListener implements ItemListener Methods: addTo(java.awt.Checkbox) addTo(java.awt.CheckboxMenuItem) addTo(java.awt.Choice) addTo(java.awt.ItemSelectable) addTo(java.awt.List) Handler arguments: object is argument to addTo event= :itemStateChanged longs= { (event.getStateChange()==event.SELECTED)?1:0 } strings= { event.paramString(), (event.getItem()).toString() }
class com.franz.jlinker.JLKeyAdapter extends KeyAdapter Methods: addTo(java.awt.Component) Handler arguments: object is argument to addTo event= :keyTyped :keyPressed :keyReleased longs= { event.getModifiers(), (event.isActionKey()?1:0), event.getKeyCode() } strings= { event.paramString() }
class com.franz.jlinker.JLMouseAdapter extends MouseAdapter Methods: addTo(java.awt.Component) Handler arguments: object is argument to addTo event= :mouseClicked :mousePressed :mouseReleased :mouseEntered :mouseExited longs= { event.getModifiers(), (event.isPopupTrigger()?1:0), event.getClickCount(), event.getX(), event.getY() } strings= { event.paramString() }
class com.franz.jlinker.JLMouseMotionAdapter extends MouseMotionAdapter Methods: addTo(java.awt.Component) Handler arguments: object is argument to addTo event= :mouseDragged :mouseMoved longs= { event.getModifiers(), (event.isPopupTrigger()?1:0), event.getClickCount(), event.getX(), event.getY() } strings= { event.paramString() }
class com.franz.jlinker.JLWindowAdapter extends WindowAdapter Methods: addTo(java.awt.Window) Handler arguments: object is argument to addTo event= :windowOpened :windowClosing :windowClosed :windowIconified :windowDeiconified :windowActivated :windowDeactivated longs= { } strings= { }
The following code examples show parts of some of the above adapter implementations. The examples illustrate how to add a new event handler that propagates the Java event to the Lisp jregister-handler interface.
When the Java object supplied with the event is also the object registered in Lisp:
package com.franz.jlinker; import java.awt.*; import java.awt.event.*; public class JLKeyAdapter extends KeyAdapter { // One addTo method is needed for each argument type. public static synchronized void addTo( Component comp ) { comp.addKeyListener( (KeyListener)(new JLKeyAdapter()) ); } // One event method is needed for each event defined in the // listener or adapter interface. public void keyTyped(KeyEvent e) { String s = { e.paramString() }; int[] l = { e.getModifiers(), (e.isActionKey()?1:0), e.getKeyCode() }; LispCall.dispatchEvent("keyTyped", (Object)(e.getComponent()), s, l); } }
When the Java object associated with the event is not the object registered in Lisp:
package com.franz.jlinker; import java.awt.*; import java.awt.event.*; public class JLActionListener implements ActionListener { private Object handle; // One addTo method is needed for each argument type. public static synchronized void addTo( Button comp ) { JLActionListener l = new JLActionListener(); l.handle = (Object)comp; comp.addActionListener( (ActionListener)l ); } // One event method is needed for each event defined in the // listener or adapter interface. public void actionPerformed(ActionEvent e) { String[] s = { e.paramString(), e.getActionCommand() }; int[] l = { e.getModifiers() }; LispCall.dispatchEvent("actionPerformed", handle, s, l); } }
Characters are converted to 16-bit positive integers for transmission and then converted back to characters using the following primitive sequences. This should yield consistent results when client and host are on the same machine.
Lisp Java Start String s s Extraction c<-(char s i) c<-s.charAt(i) Conversion n<-(char-code c) n<-(int)c Transmit 16-bit n 16-bit n Conversion c<-(code-char n) c<-(char)n Construction s<-(make-string len) sb<-new StringBuffer(len) (setf (char s i) c) sb.append(c) s <-sb.toString() Result String s s
When calling Lisp from a Java Applet, the normal mode is to advertise in Lisp and use connect() in Java.
NOTE: The behavior of the plain APPLET tag in Netscape is not reliable. The plug-in style of applet activation seems to work without problems. In Netscape this is invoked with the EMBED html tag; in Internet Explorer, the OBJECThtml tag.
When a jLinker application is running as an applet in a browser, the security measures in the browser prevent the use of run-time method lookup in the Lisp application. All methods and constructors must be named with a complete signature in the Lisp code.
One Java VM can support exactly one socket connection to Lisp (because static variables are used extensively).
If Lisp calls Java then Java may call back to Lisp before returning from the initial call, but a call to Java from the callback will block until the initial call returns. This will typically lead to a deadlock.
If Java calls Lisp then Lisp may call back to Java, but a call to Lisp from the callback will block until the initial call to Lisp returns. This will typically lead to a deadlock also.
On the Lisp side, the variable
javatools.jlinker::*transport-timeout*
may be set
to a positive number. This will trigger an error when a call to Java
blocks for more than the specified number of seconds. If the number
is larger than most reasonable delays in Java, this should detect most
deadlock situations in the Lisp code. There is no corresponding
timeout feature in Java.
In the native (JNI) implementation of jlinker, there are no recursion restrictions on calls between Lisp and Java. There is a thread restriction in ACL impementations that do not use OS threads (at this time all Unix ports): Java may call Lisp only in the thread in which Lisp initially started the Java VM. Any Lisp LWP may call Java since from the Java perspective all Lisp LWPs are the same thread.
jLinker requires two open socket connections between Lisp and Java:
By default, Lisp listens for a connection from Java and that becomes the connection for calls from Java to Lisp; Java listens for a connection from Lisp and that becomes the connection for calls from Lisp to Java.
It is also possible for Lisp to listen and accept both connections.
Intranet:
Lisp and dedicated Java VM (Lisp starts the JVM)
Lisp and separate Java application
Lisp and applet must use EMBED or OBJECT tag to run Java plugin (APPLET tag signals random errors)
Internet:
The only secure connection is to connect to Lisp from a servlet. Servlet connection to Lisp is an intranet situation.
The other forms of connection will run into firewall problems.
// sample variable settings: String lispFile = ""; String lispHost = ""; int lispPort = 4321; int pollInterval = 1000; int pollCount = 300; int javaTimeout = -1; String javaFile = ""; String javaHost = ""; int javaPort = 0; // use this to emit progress messages in Java com.franz.jlinker.JavaLinkCommon.sdebug = true; Mode-> Lisp Lisp Java Java advertises advertises advertises advertises in file at port in file at port lispFile A1 -- -- -- lispHost -- B2 -- -- lispPort -- C2 -- -- pollInterval D1 D1 -- -- pollCount E1 E1 -- -- javaFile -- -- F3 -- javaHost G1 G1 G3 -- javaPort H1 H1 H3 H4 javaTimeout -- -- I3 I3 Java call: connect(lispFile, javaHost, javaPort, pollInterval, pollCount); -- ignored A1 The pathname for the file where Lisp advertises host:port "" denotes default value "JavaToLisp.trp" D1 If Lisp advertises, then java will poll every pollInterval milliseconds. If pollInterval<0, look for Lisp exactly once E1 If pollInterval>=0 count down the pollCount value and stop polling when negative G1 This is the host name transmitted to Lisp when a connection is made to the advertising Lisp "" denotes the default host "localhost" H1 If this is 0, Java allocates a port and transmits the port number to Lisp If this is >0, Java listens at that port and transmits the port number to Lisp If this is <0, Java assumes the absval is the port number where Lisp is listening to connect to the Java server Meanwhile back in Lisp: (jlinker-init :lisp-advertises :lisp-file non-nil [:lisp-host hh :lisp-port pp] [:java-port neg]) Lisp writes the effective host:port into the file Java call: connect(lispHost, lispPort, javaHost, javaPort, pollInterval, pollCount) B2 The hostname where Lisp is advertising C2 The port number where Lisp is advertising Meanwhile back in Lisp: (jlinker-init :lisp-advertises :lisp-file nil :lisp-port non-zero-non-neg [:lisp-host hh]) Java call: advertise(javaFile, javaHost, javaPort, javaTimeout) F3 The pathname for the file where Java advertises host:port "" denotes default value "LispToJava.trp" G3 This is the host name transmitted to Lisp in the advert file "" denotes the default host "localhost" H3 This is the port number transmitted to Lisp in the advert file If this is 0, Java allocates a port and transmits the port number to Lisp If this is >0, Java listens at that port and transmits the port number to Lisp I3 the number of milliseconds that Java will advertise -1 denotes forever Meanwhile back in Lisp: (jlinker-init :java-advertises ...) Java call: advertise(javaPort, javaTimeout) H4 This is the port number where Java will advertise, it must be >0 (since otherwise, Lisp would not know where to listen) I3 the number of milliseconds that Java will advertise -1 denotes forever Meanwhile back in Lisp: (jlinker-init :java-advertises :lisp-file nil [:lisp-host hh :lisp-port pp] :java-file nil :java-host "hh" :java-port pp)
JLinker uses Java Reflection methods to make all the method calls requested by the Lisp application. When an application attempts to call a method of an inner class as in the example below:
(let* ((al (jnew "java.util.ArrayList")) (it (jcall "iterator" al))) (jcall "hasNext" it))
Java throws java.lang.IllegalAccessException
.
Our experience shows that the accessibility of inner class methods is
tested when Java reflection methods are used on them and the default
accessibility of all methods is False. If the special variable
*jlinker-set-accessible*
is set
to a non-nil
value, then jLinker will
automatically re-try the call after changing the accessibility of the
method to True.
The application programmer can avoid the overhead of a double call by evaluating a form such as
(jcall "setAccessible" m (make-immediate-object t :boolean))
for any methods known to be implemented in inner classes.
Naturally, if Java security settings prevent access to the accessibility setting of the method, then the method simply cannot be called from Lisp. One workaround in this case is to add a Java class that implements the desired method call from Java:
public class Wrap { public static boolean hasNext( java.util.Iterator x ) { return x.hasNext(); } }
The Lisp code for the previous example is then:
(let* ((al (jnew "java.util.ArrayList")) (it (jcall "iterator" al))) (jstatic "hasNext" "Wrap" it))
A single wrapper class can be used to define any number of these helper methods.
Lisp applications can interface to Java through both jlinker
implementations with the same code. The only place where the jlinker
implementation is apparent is in the call to jlinker-init. This part of the
application can be made more portable with a suitable binding for
*jlinker-init*
.
Java applications that use only LispCall
can
interface to Lisp through both jlinker implementations with the same
code.
Any part of the Java application that depends on the
LispConnector
, JavaLinkDist
, and
TransStruct
classes can only be used with the
socket implementation.
There is one important difference in the jlinker behavior that depends on the threads implementation of the Lisp image. In a Lisp implementation that uses native OS threads, Java and Lisp threads may call back and forth freely. In a Lisp implementation that does not use native OS threads, a Java application may call Lisp only from the one thread in which Lisp is running. Thus a call from Java to Lisp is possible only when Java is running in call from Lisp.
To overcome this limitation in graphic applications, the jlinker adapter and listener classes may be used to handle AWT events. These adapters and listeners queue Java events on the Java side. A Lisp process periodically polls this queue and dispatches the events to the Lisp handlers.
The mayCall() method in the LispCall
class
returns an integer that describes the thread callback restrictions:
When the interface is to a dedicated Java server, the interface is setup and controlled entirely from the Lisp application.
(jlookup :gen-preload)
.If the jLinker interface is to an independently running Java application, the steps needed to establish the interface need to be modified.
Advertise in (default) file: Lisp: (jlinker-init :lisp-advertises [:lisp-file path] [:timeout n] ...) Java: com.franz.jlinker.JavaLinkDist.connect(j2l, "", 0, -1, -1);
Lisp must advertise before Java issues the connect() call; otherwise the connect() call will fail (return false). Lisp advertises the port in the specified file.
To advertise for a limited time, call jlinker-init with
:timeout n
where n is the number of seconds to advertise.
Java: com.franz.jlinker.JavaLinkDist.connect(j2l, "", 0, pollInterval, pollCount); Lisp: (jlinker-init :lisp-advertises [:lisp-file path] [:timeout n] ...)
If Java makes the call first, then the Java program will keep checking every pollInterval (milliseconds) until Lisp starts or the count runs out.
If Lisp has made the call first, Java will connect immediately.
Java: com.franz.jlinker.JavaLinkDist.advertise(l2j, "", 0, -1) Lisp: (jlinker-init :java-advertises [:java-file l2j] ...)
Java must make the call first, otherwise, the call to jlinker-init will fail (return a list). Java advertises a port number in the given file that defaults to "LispToJava.trp", or Java may simply advertise at a pre-determined port known to the Lisp application.
The function jlinker-listen sets up a process that creates a new listener every time Java makes a new connection, so that it is always possible for Java to connect to Lisp, except for a narrow time slot when Lisp is processing a new connection. In this case, the style is always for Lisp to advertise and Java to connect.
When multiple connections are active, the code for each must run in a
separate Lisp process, and in the scope of a separate binding of
*jlinker-connection*
.
This section covers some issues that apply only to the native jlinker implementation.
On some UNIX or Linux versions, it may be necessary to modify the environment variable LD_LIBRARY_PATH to include the directory where the Java VM shared library (libjvm.so or libjvm.dylib) is located. This setting is required if the Lisp/Java application exits with the message
Error occurred during initialization of VM Unable to load native library: libjvm.so: cannot open shared object file:
If the environment variable LD_LIBRARY_PATH is needed by the Java VM,
the value must be set before Allegro CL is started. Calling (setf sys:getenv)
(after Lisp has started) is not sufficient in this case. If the Lisp
variable javatools.jlinker:*jni-ld-path-p*
is set to a non-nil
value we search the
locations specified in LD_LIBRARY_PATH for a file named
libjni.so, libjni.dll,
libjvm.dylib, or if the value of *jni-ld-path-p*
is a string, then a file with that name. If the file is not found, we
signal a continuable error.
When a Lisp image is saved with dumplisp and a native jlinker connection was running at the time of the dumplisp, the jlinker interface must be re-initialized again when the saved image is restarted. To re-initialize the jlinker interface, use the following sequence:
(jlookup :connection-instance :reset) (jlinker-init :native :load t :library "correct path to Java VM shared library")
In the Unix versions of Allegro CL we do not use native OS thread implementations. Consequently, Java methods can only call Lisp when called from Lisp initially. To allow GUI event callbacks to function, the listener and adapter classes described above can be used to queue AWT events in Java. The queue is polled periodically from Lisp and the events transferred to a Lisp scheduler. The following calls may be used to manage the poll behavior:
(jlinker-slot :max-interval [new-value]) The longest interval (in seconds) between polls. The initial value is 0.5. (jlinker-slot :min-interval [new-value]) The shortest interval (in seconds) between polls. The initial value 0.075. (jlinker-slot :event-group [new-value]) The maximum number of events to dequeue at each poll. The initial value is 5.
If the Lisp application call the AWT "dispose" method for an object where "isDisplayable" is "false", the Java VM will not return to Lisp and the entire application will hang in a non-interruptible state. We recommend a form such as
(when (jcall "isDisplayable" x) (jcall "dispose" x))
All the following classes and methods are defined in Java package
com.franz.jlinker
and supplied in the
file jlinker.jar.
We include here a complete example of a simple program.
(in-package :user) ;;(set-case-mode :case-sensitive-lower) (require :jlinker) (use-package :javatools.jlinker) (defpackage :javatools.jlinker (:nicknames :jl)) ;; Make sure the required files are locally visible ;; customized copy of [Allegro directory]/jlinker/jl-config.cl ;; [Allegro directory]/jlinker/jlinker.jar (load "jl-config") (defun new-tokenizer (&optional (string "A B C D ") (delimiters " ")) (jnew (jconstructor "java.util.StringTokenizer" "java.lang.String" "java.lang.String") string delimiters)) (defun next-token (inst) (jcall (jmethod "java.util.StringTokenizer" "nextToken") inst)) (defun run-tokenizer (&optional (string "A B C D ") (delimiters " ")) (or (jlinker-query) (jlinker-init)) (let ((inst (new-tokenizer string delimiters)) res) (dotimes (i (jcall (jmethod "java.util.StringTokenizer" "countTokens") inst)) (push (next-token inst) res)) (values inst (reverse res)))) ------------------- console log: --------------------- cl-user(4): :ld example ; Loading C:\mmWork\java\fi\java-cur\example.cl ; Loading C:\mmWork\java\fi\java-cur\jl-config.cl cl-user(5): (run-tokenizer) ; Fast loading from bundle code\acldns.fasl. #<tran-struct Java IP 1004,118185548 java.util.StringTokenizer> ("A" "B" "C" "D") ;; the following example shows how a Java error ;; is mapped to a Lisp error cl-user(6): (next-token *) Error: Java error: java.util.NoSuchElementException result= "java.util.NoSuchElementException" Restart actions (select using :continue): 0: Supply another value. 1: Return to Top Level (an "abort" restart) 2: Abort #<process Initial Lisp Listener(6d8)> [1c] cl-user(7): :pop cl-user(8):
There are additional code examples in <Allegro directory>/examples/jlinker/*, including:
applet/ examples of Java applets connected to Lisp. javabean/ examples of Java Beans connected to Lisp. servlet/ examples of Java servlets connected to Lisp. timecard/ a complete Lisp application using the Java AWT classes for the user interface.
The jLinker Java Bean API facilitates the creation of Java Bean classes that call Allegro CL functions to do the work of the Java Bean. The jLinker Servlet API facilitates the creation of Java Servlets that call Allegro CL functions to do the work of the Servlet.
The extensions are loaded with the forms
(require :jlinker) ;; Available to all customers. ;; returns NIL if jlinker is already ;; loaded. jlinker module must be ;; loaded before jlinkent module. (require :jlinkent)
jLinker includes support for Java Servlets and Java Beans. The Java support consists of Java classes that implement communication between a Java HttpServlet and a Lisp image. The Lisp support consists of classes and functions that implement the Lisp side of the interface. We also include examples of simple servlets where the work of the servlet is performed in Lisp.
The jLinker Java Bean API facilitates the creation of Java Bean classes that call Allegro CL functions to do the work of the Java Bean.
All Lisp symbols are in package :javatools.jlinker
.
The example code in examples/jlinker/javabean is described in the file readme.txt.
The jLinker Servlet API facilitates the creation of Java Servlets that call Allegro CL functions to do the work of the Servlet.
All Lisp symbols are in package javatools.jlinker
.
Java signatures are taken from "Java Servlet API Specification - Version 2.1a - November 1998" from Sun Microsystems at http://java.sun.com/products/servlet/.
The example code in examples/jlinker/servlet is described in the file readme.txt.
The javatools.jlinker::servlet
class is the
superclass of all the Lisp implementation classes that support the
servlet interface. It has slots:
instances
:
This is a class slot that keeps track of
the number of instances of the class.
instance
:
An instance slot that identifies each instance uniquely.
java-classes
:
A list of strings naming Java classes that
may invoke this Lisp class. Access from
other Java classes causes an error signal.
The following functions and methods are defined:
init()
method. The
lisp-class-name argument must be a string containing a fully qualified
class name and the class must be a sub-class of http-servlet
.
(
self
javatools.jlinker::servlet)
. This method is called from the
Java servlet destroy()
method. The pre-defined
primary method discards any locally cached information and any remote
references.
(
self
javatools.jlinker::servlet)
. Returns the ServletConfig
reference saved from the call to init()
.
(
self
javatools.jlinker::servlet)
.
Retrieve from Java a reference to the ServletInfo
object.
The http-servlet
class is a subclass of javatools.jlinker::servlet
. This is the Lisp counterpart to the Java class LispHttpServlet
.
This class implements dummy methods for all the Java methods in the
Java class HttpServlet
. User code should subclass
this class and override any method definitions that are actually used
by the application. The subclass must also define a value for
java-classes slot
.
The predefined dummy methods are:
(
self
http-servlet
)
request response
(
(self
http-servlet
)
request response
(
(self
http-servlet
)
request response
(
(self
http-servlet
)
request response
(
(self
http-servlet
)
request response
(
(self
http-servlet
)
request response
(
(self
http-servlet
)
request response
These are classes that should be subclassed by the application. The
subclass defines working methods for the above generic functions. The
subclass also defines a value for the java-classes
slot:
Two start-work
methods are defined on instances of those classes. The argument list
is (
self
async-http-servlet
)
work request response gate
and
(
self
multi-async-http-servlet
)
work request response gate
.
public void init(ServletConfig config) Java Method
The Java method invokes the Lisp function new-servlet to propagate this method call.
public void service(...) Java Method
Handled by the Java super-class implementation.
public void destroy() Java Method
The Java method calls the Lisp destroy method.
protected void doDelete(HttpServletRequest request, Java Method HttpServletResponse response) throws ServletException;
The Java method calls the Lisp do-delete method.
protected void doGet(HttpServletRequest request, Java Method HttpServletResponse response) throws ServletException;
The Java method calls the Lisp do-get method.
protected void doHead(HttpServletRequest request, Java Method HttpServletResponse response) throws ServletException;
The Java method calls the Lisp do-head method.
protected void doOptions(HttpServletRequest request, Java Method HttpServletResponse response) throws ServletException;
The Java method calls the Lisp do-options method.
protected void doPost(HttpServletRequest request, Java Method HttpServletResponse response) throws ServletException;
The Java method calls the Lisp do-post method.
protected void doPut(HttpServletRequest request, Java Method HttpServletResponse response) throws ServletException;
The Java method calls the Lisp do-put method.
protected void doTrace(HttpServletRequest request, Java Method HttpServletResponse response) throws ServletException;
The Java method calls the Lisp do-trace method.
public static Object[] newGate() Java Method
Return a new closed gate.
public static void testGate(Object[] gate) Java Method
Wait for gate to open and return a String x.
x.length()=0 if operation completed x.length()>0 if error or failure, string contains message
public static Object[] lispValues Java Method (res, called, min, max, firstRefP)
Utility function called by the sample implementations of LispHttpServlet and LispAsyncHttpServlet to decode the result array returned from a call to Lisp.
res - result array returned from Lisp called - the name of the Lisp function called min - the minimum number of expected values max - the maximum number of expected values firstRefP - first returned value should be a remote reference to a Lisp object
returned value is an array Object[2] where the first element is an Integer return code and the second element a String error message.
As described in Section 2.4 Dynamic Linkage Java Reference, various classes and methods defined in earlier jLinker releases now have equivalents in the LispCall and LispConnector classes (discussed in Section 2.4 Dynamic Linkage Java Reference). The equivalent calls in LispCall allow full portability between socket and native modes.
Use of the classes and methods described next is deprecated. These
definitions are retained for compatibility with existing applications
only. All these methods may only be used with the socket
implementation of the jlinker interface. The only exceptions are the
subclasses of JavaLinkDist.JLinkerException
.
public class TranStruct
: this class represents
remote references passed between Java and Lisp.
All the methods below are defined in class JavaLinkDist
.
public static TranStruct newDistOb (boolean x)
public static TranStruct newDistOb ( Object x )
:
These methods are used to create a remote reference to a Java value or object so that it may be passed to Lisp.
There is one method for each primitive
Java type, for int[]
, for String[]
,
and for Object
.
public static TranStruct nullDistOb ()
This method is needed to create a remote reference to Java null
or to Lisp
nil
.
static public boolean nullP (TranStruct str)
public static boolean booleanP(TranStruct)
static public boolean integerP (TranStruct str)
static public boolean realP (TranStruct str)
static public boolean stringP (TranStruct str)
static public boolean errorP (TranStruct str)
static public boolean pointerP (TranStruct str)
public static boolean boolValue(TranStruct)
static public int intValue (TranStruct str)
static public double doubleValue (TranStruct str)
static public char charValue (TranStruct str)
static public char charValue (TranStruct str, int j)
static public char charValue (TranStruct str, int j, int i)
static public String stringValue (TranStruct str)
static public String stringValue (TranStruct x, int i)
public static Object pointerValue(TranStruct)
These predicates and accessors are used to test and extract primitive Java values from remote references. The TranStruct class acts as a tagged wrapper for values passed between Lisp and Java. The predicates determine the type of the primitive data in the wrapper and the other functions are accessors for the value.
Thus, if integerP(x)
is true,
intValue(x)
returns the integer value inside the
wrapper. Similarly for realP
and
stringP
.
pointerP
is true
for remote ref objects.
If pointerP(x)
or errorP(x)
is
true, then stringValue(x)
is the symbol-name of the
Lisp type of the object.
stringValue(x, 0)
is equivalent to
stringValue(x)
.
stringValue(x, 1)
is defined only when
errorP(x)
is true in that case, it returns the "~A"
representation of the Lisp error.
For stringValue(TranStruct x , int i)
, When x is a
remote reference to a symbol then stringValue
should not be used. Use symbolName
and
symbolPackage
instead.
The type information extracted by stringValue(x, 0)
is truncated to 50 characters.
NOTE: this is an incompatible change from Version 3.0.8 or earlier. In the earlier versions, the type string was not limited in length, and Lisp symbols appeared simply as remote reference pointers with the type string set to "SYMBOL".
public static TranStruct[] invokeInLisp ( int style, TranStruct op, TranStruct args[] ) public static TranStruct[] invokeInLisp( int style, String op ) public static TranStruct[] invokeInLisp( int style, String op, int arg ) public static TranStruct[] invokeInLisp ( int style, String op, String arg )
This method is used to invoke a Lisp function.
The second argument may be a remote reference to a string containing a fully qualified name of a symbol that represents a Lisp function. It may also be a remote reference to a symbol or a function obtained in a previous call to Lisp. The third argument is an array of argument references.
The first (style) argument passed to Lisp may be one of 5 integer values:
When the style argument is -1 or 0, the method invokeInLisp returns an array of length zero.
When the style argument is 1 or 2, the method invokeInLisp returns an array of values, which represents the list of values returned by the Lisp function. If the array size is 1, and the array element is an immediate string reference, the Lisp function signaled an error and the string contains a description of the error. If array element 0 is not an immediate string reference, it is an immediate integer reference, and represents the number of values returned by the Lisp function.
When the style argument is 3, the behavior is similar to a one-way
call. The Java side does not wait for the Lisp operation to finish.
The Lisp operation is started in a new Lisp light-weight process
(thread) to prevent blocking of the interface. The value returned
immediately from Lisp to Java is a remote reference
(TranStruct instance)
to a Lisp list of the form
(status . rest)
. The car of this list may be
queried from Java to determine the progress of the operation.
When status is a negative value greater than -99, that indicates an operation in progress as described next. The value will keep changing as the operation progresses. A value of -99 indicates a cancelled operation, the state will not change any more. When status changes to zero or a positive value, the operation is complete and the rest component is a list of the value returned.
The following status
values have the indicated
meanings:
status
should shortly
become 0 or positive)
Package: com.franz.jlinker Class: JavaLinkDist Method: invokeInLispEx public static TranStruct[] invokeInLispEx( int style, String op, int arg ); public static TranStruct[] invokeInLispEx( int style, String op, String arg ); public static TranStruct[] invokeInLispEx( int style, String op ); public static TranStruct[] invokeInLispEx( int style, TranStruct op, TranStruct args[] )
This method is like invokeInLisp, but it throws one of the subclasses
of JLinkerException if the call does not result in a normal value
returned from Lisp. Consequently, if res is a variable holding the
returned result, intValue(res[0])
is the
number of values returned from Lisp; res[1]
is the
first value returned from Lisp, and so on through
res[n]
, which is the n-th value returned from Lisp.
For example, The following is an example of an asynchronous call to Lisp (style=3):
import com.franz.jlinker.JavaLinkDist; ... // Let myfunc be a Lisp function that takes a while to // compute a string value. TranStruct[] res = invokeInLispEx(3, "myfunc"); // We can safely assume that exactly one Lisp reference was returned TranStruct[] args = new TranStruct[] { res[1], newDistOb(0) }; TranStruct elt = newDistOb("elt"); int flag = -1; while ( flag==-1 ) { Thread.sleep(100); // Call Lisp to fetch a new flag value res = invokeInLispEx(2, elt, args); flag = intValue(res[1]); } if ( 0<flag ) { arg[1] = newDistOb(1); // Call Lisp to fetch the second element in the list res = invokeInLispEx(2, elt, args); String v1 = stringValue(res[1]); }
public static void discardInLisp (TranStruct str)
The purpose of this method is to indicate to Java and to Lisp that a remote reference is no longer in use. Any further use of the argument will result in an error signal.
public static boolean query() public static boolean query(boolean verify) public static void disconnect()
The query function returns true if the interface is available. The verify argument, if true, requests a round-trip message to Lisp to verify that Lisp is actually there.
public static boolean connect(String j2l, String javaHost, int javaPort, int pollInterval, int pollCount) public static boolean connect(String lispHost, int lispPort, String javaHost, int javaPort, int pollInterval, int pollCount)
The connect method attempts to connect to a Lisp server. The first form looks for Lisp advertising in the file specified in the j2l argument. The second form attempts to connect to a Lisp server listening at the specified host and port.
If pollInterval is -1, only one attempt is made. Otherwise pollCount attempts are made pollInterval milliseconds apart.
j2l "" -> "JavaToLisp.trp" javaHost "" -> "localhost" javaPort 0 -> system assigned lispHost "" -> "localhost" lispPort 0 -> error, must be >0
public static boolean advertise(String l2j, String host, int port, int timeoutSeconds) public static boolean advertise(int port, int timeoutSeconds)
The first form of the advertise method advertises Java in the file JavaToLisp.trp or the one specified in the l2j argument, and waits until Lisp makes a connection. The second form does not write a file, but simply listens at the specified port which must be greater than zero. If timeoutSeconds is -1, wait forever.
public static boolean booleanP(TranStruct); public static boolean boolValue(TranStruct); public static Object pointerValue(TranStruct);
If booleanP(x) is true, then boolValue(x) will return the boolean value referenced by x. If pointerP(x) is true and x is reference to a Java object then pointerValue(x) is that Java object; if x is a reference to an immediate value, then pointerValue(x) is the Java object form of that value; otherwise, x must be a remote reference to an object in Lisp, and x itself is returned.
public class com.franz.jlinker.LispConnector { public static boolean lispAdvertises = true; // false -> Java advertises public static boolean advertInFile = false; // true -> advertise in file public static String lispFile = ""; // use default "JavaToLisp.trp" public static String lispHost = ""; // use default "localhost" public static int lispPort = 4321; public static int pollInterval = 1000; public static int pollCount = 300; public static int javaTimeout = -1; // wait forever public static String javaFile = ""; // use default "LispToJava.trp" public static String javaHost = ""; // use default "localhost" public static int javaPort = 0; public static boolean debug = = false; }
This class implements a generic connection to Lisp. The typical way to use this is to create a subclass that overrides the go method with one that sets the connection variable to appropriate values and then calls the superclass go method.
public static boolean go(boolean verify, String[] throwErr)
This method tests the connection to Lisp.
If the connection succeeds, return true. If the connection fails, the result depends on the value of the throwErr argument.
While integerP()
returns true for any integer value (byte, short or
int) the following predicates return true only when
a specific type was encapsulated in an immediate
object.
public static boolean byteP(TranStruct)
Returns true if and only if a byte value was stored into an immediate
transfer object.
If byteP(x)
is true,
intValue(x)
may be safely cast to byte.
public static boolean shortP(TranStruct)
Returns
true if and only if a short value was stored into an immediate
transfer object. If shortP(x)
is true,
intValue(x)
may be safely cast to short.
public static boolean intP(TranStruct)
Returns true if and only if an int value was stored into an immediate transfer object.
public static boolean longP(TranStruct)
Returns
true if and only if a long value was stored into an immediate transfer
object. If longP(x)
is true then
longValue(x)
returns the value,
intValue(x)
returns some number of low-order bits
of the long value but the number of bits is implementation-dependent.
public static boolean charP(TranStruct)
Returns
true if and only if a char value was stored into an immediate transfer
object. If charP(x)
is true then
charValue(x)
returns a valid character.
public static boolean singleP(TranStruct)
Returns
true if and only if a single value was stored into an immediate
transfer object. If singleP(x)
is true, then
doubleValue(x)
may be safely cast to float.
public static boolean doubleP(TranStruct)
Returns true if and only if a double value was stored into an immediate transfer object.
The following are also useful.
public static long longValue(TranStruct)
If
longP(x)
is true then
longValue(x)
returns the value, otherwise some
random value.
public static TranStruct newDistOb ( long x)
Creates a new immediate transfer object containing the long value.
public static boolean symbolP(TranStruct)
Returns true if argument is a remote reference to a Lisp symbol.
public static String symbolName(TranStruct x)
If
symbolP(x)
is true, then this function returns the
symbol name component of the remote reference.
public static String symbolPackage(TranStruct x)
If symbolP(x)
is true, then this function returns
the symbol package component of the remote reference.
public static int symbolCaseModes(TranStruct x)
If symbolP(x)
is true,
then this function returns a value of
the form 0xvvppnn
, where
pp is a package name qualifier nn is a symbol name qualifer 00 = read 01 = intern 02 - find vv is a symbol reference qualifier 00 = symbol reference 01 = symbol-value 02 = symbol-function 03 = class
public Static TranStruct newDistSym(String pname, String pkg, int caseflag)
To create a remote reference to a Lisp symbol. The caseflag argument is a number of the form 0xppnnvv as described in the description of symbolCaseModes(TranStruct x) just above.
Java NOTE: There are no constructors in Java to generate a transfer object containing an array of byte, short, or float (and Lisp does not expect or recognize these). These arrays can be converted in Java to arrays of int or double and passed to Lisp that way.
Copyright (c) 1998-2009, Franz Inc. Oakland, CA., USA. All rights reserved.
Documentation for Allegro CL version 8.1. has had minimal revisions compared to the 8.0 page.
Created 2007.4.30.
| Allegro CL version 8.1 Minimally revised from 8.0. 8.0 version |