Subject: Re: Interfacing Java and Prolog both ways

From: Paul Singleton <[email-masked]>
Date: Mon, 24 Jan 2000 01:11:41 +0000

Fred Dushin wrote:

> Embedding a java VM in prolog seems like a fun idea, but I don't know
> what utility that would have, exactly.

Lots!  I've been working on this embedding since late summer 1999 when I
started to port all my work-in-progress from Quintus+Solaris to SWI+NT4.

Rather than rewrite lots of Solaris-specific foreign code to be
NT-specific,
I set out to use Java for all "foreign" activities.

Java offers Prolog free, platform-independent access to host
filesystems,
networks, graphics, umpteen APIs for things like telephony and email,
all
those Beans, XML parsers etc. etc. etc.

I'm convinced that Prolog should "leverage" all of this good stuff, as
Prolog systems will never otherwise acquire such a comprehensive and
rapidly-growing collection of portable, non-proprietary interfaces to
the Real World... 

And via a Java-COM bridge you can manipulate ActiveX Components, e.g.
not
only MS controls but applications such as Word, Excel etc. (these must
be
running on a Win32 host, but it can be a remote one :-)

> That's fine, but what I am after is a general enough interface so that Java
> classes and methods can be invoked seamlessly from Prolog without the user
> having to touch any C.

My interface (its working name is "JSP" for "Java Serves Prolog") is
modelled on XPCE, hence:

    jsp_new( +Classname, +Args, -Ref) :-

    jsp_get( +Ref, +FieldName, -Value) :-

    jsp_set( +Ref, +FieldName, +Value) :-

    jsp_call( +Ref, +MethodName, +Args, -Result) :-

(these preds also handle arrays, by slightly inelegant overloading)

as in:

    jsp_system_properties( NVs) :-
        jsp_call( 'java.lang.System', getProperties, [], Ps),
        jsp_call( Ps, propertyNames, [], En),
        jsp_system_properties_1(En, Ps, NVs).

    jsp_system_properties_1( En, Ps, NVs) :-
        (   jsp_call( En, hasMoreElements, [], @true)
        ->  jsp_call( En, nextElement, [], N),
            jsp_call( Ps, getProperty, [N], V),
            NVs = [N-V|NVs2],
            jsp_system_properties_1( En, Ps, NVs2)
        ;   NVs = []
        ).

which calls the (static) method getProperties() of the java.lang.System
class to get a Properties structure which contains the JVM's
environment,
then calls the latter's propertyNames() method to get an enumeration
of the names, then tail-recursively gets each named property and
builds them into a list of Name-Value pairs:

  ?- jsp_system_properties( NVs),
  |    member( NV, NVs),
  |    write( NV), nl,
  |    fail.
  user.language-en
  java.home-d:\jdk1.1.8\bin\..
  java.vendor.url.bug-http://java.sun.com/cgi-bin/bugreport.cgi
  awt.toolkit-sun.awt.windows.WToolkit
  file.encoding.pkg-sun.io
  java.version-1.1.8
  file.separator- (\)
  ... [etc.]

> Unfortunately, the only way I can see to do this would be to
> implement the JNI as a set of foreign predicates. This is a
> rather large undertaking, however,

Yes, it was :-/ 

> and it would be incredibly cumbersome on the programmer.

Yes, but the JNI mapping is just the infrastructure: the higher
level stuff invisibly finds and loads classes, gets and caches
methodIDs and fieldIDs, and generally does all the housework
necessary to support dynamic instantiation of classes by name,
calling methods by name, and getting and setting fields by name.

It also has to mimic the Java compiler's resolution of overloaded
methods by locating the "most-specific" method to which the given
actual parameters are assignable.

NB I've managed to load SWI-Prolog, JSP and JPL all together! but
they need a bit of integration.

Current issues:

 * efficiency: the low-level code was largely prototyped in Prolog,
   but rewritten in ANSI C as I came to understand and solve the
   problems; the higher-level code is entirely in Prolog, and rather
   expensive, but with lots of scope for smarter caching, and for
   compilation to more specialised clauses.

 * safety: the JVM imposes some fairly stringent preconditions on
   the JNI calls, and may crash if these are not met.  The higher-
   -level routines should guarantee that the JVM is kept happy, but
   use of the lower-level calls requires care, else the JVM can crash
   and take Prolog with it.  I think it's feasible to maintain enough
   metadata beneath the lowlevel interface to validate all calls (?)

 * garbage collection: this is where I need some help from Jan:
   unlike XPCE, the object references returned by JSP are

       @atom

   (rather than @integer), as I'm hoping to hitch a ride on atom
   garbage collection.  The atoms are made from the object address,
   e.g.

       @'J#4294966930'

   and whenever an atom of this format is garbage-collected, I'd like
   to extract the JVM reference and garbage-collect that too.

   (If the Prolog application happens to use similar atoms, then some
   JVM garbage won't be recovered, and some detectably spurious JVM
   DeleteGlobalRef() calls will be attempted, but I think this is still
   sound...)

 * type mapping:

                          boolean  <->  @true, @false

     byte, short, char, int, long  <->  integer

                    float, double  <->  float

                 java.lang.String  <->  atom

      other class & array objects  <->  @refatom

                             null  <->  @null

                             void   ->  @void

 * exception handling:

   Java/JVM exceptions are checked for after each JNI call, but
   currently JSP just prints a warnign message.  I guess we should
   map them into Prolog exceptions...

 * event handling:

   asynchronous events could either call Prolog via JPL, or could
   awaken a sleeping Prolog execution (this case is what I failed to
   explain clearly in an earlier message: I want JSP to be able to
   do the equivalent of an XPCE 'confirm', i.e. go to sleep, and
   Java code to be able to perform the equivalent of XPCE's 'return',
   to force/allow the Prolog goal to continue.  I haven't thought
   about how to do this)

Finally an example which creates a new Word document, plants some
text in it and trivially reformats it (via an evaluation copy of a
proprietary "pure Java" Java-COM interface):

        jsp_new( 'ji.ms.word.Application', [], A),
        jsp_call( A, setVisible, [@true], Void),
        jsp_call( A, getDocuments, [], Ds),
        jsp_call( Ds, add, ['Normal.dot',@null], D),
        jsp_call( A, getSelection, [], Sn),
        jsp_call( Sn, typeText, ['Hello World'], Void),
        jsp_call( Sn, wholeStory, [], Void),
        jsp_call( Sn, getFont, [], F),
        jsp_call( F, setSize, [18], Void).

Paul Singleton