Java-Prolog bridge

From InterProlog Wiki
Jump to: navigation, search

What is it

A SDK for:

  • Java developers to embed and execute Prolog code (local or in remote servers) in their apps
  • Prolog programmers to:
    • use any Java system services or components available to the current Java VM
    • develop user interfaces for their apps; for this though Prolog Studio is preferable, being a super set of the bridge.

It is assumed that a Java application launches one or more Prolog engines under its control. InterProlog provides Java with the ability to call any Prolog goal through a PrologEngine object, and for Prolog to call back any Java method through a javaMessage predicate, while passing virtually any Java objects and Prolog terms between both languages. Both primitives are recursive and open-ended type wise, unlike in other less powerful systems.

import com.declarativa.interprolog.*;
ProcessEngine engine = new XSBSubprocessEngine("MyXSBpath");
if (engine.deterministicGoal(
   "javaMessage('java.lang.System'-out,println(string('Hello from Prolog, Java world!'))"
  )
	System.out.println("This goal succeeded");
engine.shutdown();

The above Java snippet calls a Prolog goal, which calls back a Java method printing into System.out.

InterProlog comes with a Java application opening up a Prolog read/eval loop window (com.declarativa.interprolog.gui.XSBSubprocessEngineWindow.main(...)), and a few simple Prolog term visualization aids, but there's more in Prolog Studio.

Historical note: This tool originated in another company; older versions and obsolete information can be found [there].

Java side API

See the [Javadoc]

Prolog side API: easier

The following predicate is available from Prolog, and lets one synchronously send a message to the Java side:

java( Target, Result, Exception, method(Parameters) )

In addition to this there are simplified versions of it:

java( Target, Result, method(Parameters) )
java( Target, method(Parameters) )

Both of these fail if an exception is thrown. The second ignores any result. Most people will only need java/3 and java/2. In addition to all these there's a slightly more powerful variant, javaMessage #Prolog side API: harder.

Quick examples

Sending a message to a Java String object:

java( string('InterProlog Java bridge'), string(Middle), substring(int(12),int(16)) ).
...we get:
Middle = Java.

The first argument of predicate java/3 is the target of the message, the second the result, and the third is the message; notice that each of the two arguments has a int(..) functor: that indicates that the argument should match a Java int (basic) type. This and other type annotations are detailed below.

Another example, to be heard:

java('java.awt.Toolkit',T,getDefaultToolkit), java(T,beep).
...we get:
T = 3 % may be a different number in your system

The target argument can also be a class name, and in that case the message is assumed to match a static method. T gets bound to a reference to an object on the Java side.

As you can see above Prolog can use any public static methods from any public class; but to invoke instance methods you need... an object! And not just some toy String as fabricated above at Prolog's request. For any real app usually it will be necessary for the Java side to store important object references on the Prolog side:

 PrologEngine engine = ...;
 SomeClass object = ....;
 engine.command("assert( myFavoriteObject(" + engine.registerJavaObject(object) + ") )")

...so that later it is possible to write this Prolog fragment:

..., myFavoriteObject(Target), java(Target,Result,someMethod(..args..)), ...

Actually, the system always asserts one such object reference, to the (Java object representing the...) Prolog engine itself:

ipPrologEngine(E)

E is a reference to the PrologEngine object that launched this Prolog process. Say that for some reason you want to find where the main bridge system file is...:

ipPrologEngine(Engine), java(Engine,string(Path),getInterprologPath).

Object references can be represented as integers on the Prolog side. More details below.

Special messages for array access

To bind N to array A's length:

 java(A,N,length).

For a direct "getter" to a Java array object A:

 java(A,ThirdElement,[2]).

ThirdElement will be bound to a copy of the a[2], assuming a to be the array on the Java side.

Java type annotations for Prolog terms

Java and Prolog data representations differ, so it is necessary to provide some hints to the java(...) predicates for data conversion.

NOTE: the following annotations are NOT available for the javaMessage(...) predicates further ahead, which work a bit differently.

Basic types and some objects

The next table lists Java basic types as used in method declarations, and examples of the acceptable Prolog terms to use:

Java formal types Prolog actual values Comments
byte, short, int, long byte(13), short(2014), int(10000000), long(4294967296) integers
float, double float(3.14), double(2.718) floating-point
char char(65) meaning 'A'
boolean boolean(1) 1 for true, 0 for false

The next lists some current object types:

Java formal types Prolog actual values Comments
Byte, Short, Integer, Long 'java.lang.Byte'(13), 'java.lang.Short'(2014), 'java.lang.Integer'(10000000), 'java.lang.Long'(4294967296) integers (wrapper objects)
float, double 'java.lang.Float'(3.14), 'java.lang.Double'(2.718) floating-point (wrapper objects)
Character 'java.lang.Character'(65)
Boolean 'java.lang.Boolean'(1) 1 for true, 0 for false
String string('some Prolog atom')
String[] array(string,['Lisbon','Seattle','New York City']) Notice that the array elements do not need annotations
byte[] array(byte,[1,-2,3,4,-5,6,7,8,9])
int[] array(int,[1,2,3,4,5,-6,7,8,9])
float[] array(float,[1,2,3,4,5,-6,7,8,9])
TermModel term(E=M*C*C) This would convert to/from a Java tree with 3 String nodes and 4 variable nodes. See [Javadoc]
TermModel[] terms([E=M*C*C, 13, 'Coherent')] This would convert to/from a Java array of TermModels (term trees). See [Javadoc]
an Exception thrown exception(ClassName,Message) If an exception occurs during java(T,Result,Exception,Message).

Otherwise Exception = null javaMessage/7 below gets it in a different, raw form.

Examples:

 ?- java(string(hello),R,getBytes).
 R = array(byte,[104,101,108,108,111]
 ?- java('java.lang.String',R,'String'(array(byte,[119,111,114,108,100]))).
 R = string(world)

Some special cases

The next table mentions some special cases:

Java side Prolog side Comments
null null
a Class object 'java.lang.System' a Prolog atom C denotes the Java class C
a static field 'java.lang.System'-out class_name - static_var_name
any object registered in the engine a Prolog integer the Java programmer uses registerJavaObject(object)

These are all the conversions supported by the InterProlog Java bridge... out of the box. But by teaching [[) JavaDoc]] other Java objects to Prolog it is possible to handle virtually any serialisable object, by growing the ipObjectSpec/4 relation described below, and with a bit more work on the Prolog side.

Prolog side API: harder

The previous java(...) predicates are implemented over the lower level javaMessage(...) and ipObjectSpec/4 predicates in this section. Let's start by going under the bridge's hood.

Invoking Java methods from Prolog - the whole story

To fully understand the data conversion issues, it is important to realize that messages from Prolog to Java go through the following execution steps:

  1. the message and its arguments are serialized by Prolog and sent to Java onto a wire
  2. Java unserializes the message, [invokes it], and serializes the result back to Prolog
  3. the result is unserialized by Prolog

The InterProlog Java bridge includes a Prolog grammar which translates between Java serializations (byte streams) and some specific Prolog terms - term representations of the grammar non terminals. Actually, it implements the stream protocol for [Java serialization].

For example, if Java serialises a wrapped integer, e.g. the object new Integer(13), the resulting Prolog "raw" term is:

object(
class(java.lang.Integer,long(316842148,-142506184),classDescInfo([int(value)],2,
  class(java.lang.Number,long(-2035509987,194306187),classDescInfo([],2,
     null)))),
[] + [] + [13] 
)

This is a complete description of (a copy of) a Java Integer instance. The Java bridge provides predicates such as

javaMessage( Target,Result,Exception,MessageName,ArgList,NewArgList ) 

... where other than MessageName (a String) all other elements (Target etc) will be in the "raw" form depicted above

So although the system gets objects across both languages with step 2 - effectively interfacing with Prolog via huge "raw" terms such as the one above - steps 1 and 3 do require some Prolog developer attention. This developer "glue" is written with the help of one predicate:

ipObjectSpec(ClassNickname,ObjectTemplate,PlaceholderVariables,ExamplePair).

This predicate is able to digest/generate the above complex object(......) terms from/to Java. For example, the goal:

?- ObjectSpecification = object(
class(java.lang.Integer,long(316842148,-142506184),classDescInfo([int(value)],2,
  class(java.lang.Number,long(-2035509987,194306187),classDescInfo([],2,
     null)))),
[] + [] + [13] 
), ipObjectSpec(ClassName, ObjectSpecification,[Int],_), writeln(Int).

...digests the beast and prints "13". And the similar goal

ipObjectSpec('java.lang.Integer', ObjectSpecification,[13],_).

... binds ObjectSpecification to that huge "raw" term, generating it - typical Prolog bi-directionallity at at work.

So if, for another example, variable GUI is bound to a reference to some Swing text field in the UI which currently contains the text "Hello!", goal

javaMessage(GUI,R,getText), javaMessage(R,LSpec,length), ipObjectSpec('java.lang.Integer',LSpec,[L],_).

... will bind L to 6.

We'll be back to ipObjectSpec in a moment. Now some more details on the javaMessage predicates.

Low-level messaging predicates

javaMessage( Target,Result,Exception,MessageName,ArgList,NewArgList ) 

Synchronously sends a message to Java object Target, waiting for its Result, catching any Exception that may occur. There are sugared versions below. In any case, arguments in ArgList must be of the proper Java-compatible types, in the form of object specifications. NewArgList contains the same objects in ArgList after the message is processed, possibly reflecting state changes.

The messages available are those documented as public constructors and methods on the Java classes being used.

javaMessage(Target,Result,Message) 

Same as javaMessage/6, but accepts the Message in methodName(arguments) format, neglects the new state of the arguments, and treats some Target cases, avoiding the need for common object specifications (accepting simpler forms): object reference (integer), class object (atom), and class variable (class-variable term) - effectively the same cases already seen above #Some_special_cases, but no more.

javaMessage(Target,Message)

Same as javaMessage(Target,_,Message)

In conclusion:

Object/term conversion

The following predicates assist in data conversion and are needed to use javaMessage, as well as within the predicates called from the Java side (via the deterministicGoal methods).

The first was already shown above:

ipObjectSpec( Name,G,Vars, examples-[SubstA,SubstB]/ANames) 

One such fact is made available for each ObjectPairExample(Name,A,B) instance that the Java programmer sent to Prolog, either on startup or later through teachMoreObjects(); Name is the name of the class as given by the Java programmer - typically the fully (package) qualified class name; objects A and B are compared, producing a generalizing object (specification template) G plus variable list Vars, that if bound to either SubstA or SubstB would become A or B resp. For the meaning of ANames see ipObjectTemplate.

So for a particular serialisable class, only those fields of interest need to be exposed to Prolog - by making them different between A and B. Today this could be reimplemented using Java annotations, but these were made available well after the InterProlog bridge was designed.

The next predicates are redundant variants os this, which may be useful in some situations:

ipObjectSpec(ClassName,VarsValues,Object)

If Object is a variable, bind it to an object specification similar to the prototype/example given for the class except for the differences in VarValues; otherwise VarsValue will be bound to the differences between Object and prototype.If the class is an array, VarValues will be a list simply with the array values. Otherwise VarValueList is a list [VarName1=Value1, ..., VarNameN=ValueN]. Each VarName must be an atom, the name of a Java instance variable of the classEach Value must be compatible with the corresponding object field; this is only partially checked, as not all information is available on the Prolog side.

ipObjectTemplate(Name,Template,ANames,TVars,TSubs) 

One such fact is made available for each ObjectPairExample(Name,A,B) instance that the Java programmer sent to Prolog, either on startup or through teachMoreObjects(); object A is analysed, and all variables in its class description are replaced by logic variables, collected in TVars, with values collected in TSubs; the resulting object specification is Template; binding TVars to TSubs would make Template = A; the variable types(names) are collected in ANames.

The following is a higher level predicate pair, the Prolog counterparts to the Java representation for generic Prolog terms, [TermModel]

buildTermModel( Term,TermModel ) / recoverTermModel( TermModel,Term ) 

Builds/recovers an object specification for a tree of TermModel instances, representing a Prolog Term; used by browseTerm (see #Visualization_of_Prolog_data) or when needs to pass a full Prolog term to Java. Prolog variables are mapped into numbered instances of a dedicated Java class, VariableNode.

More about object specification

As already stated, Strings and null objects have trivial specifications: string(Atom) and 'null', resp. Other cases have either ipObjectSpec or ipObjectTemplate tuples:

ipObjectSpec('InvisibleObject',X,[ID],_)

Object X should be the object already existing and registered as (int) ID on the Java side. Both javaMessage(...) and java(...) consider that an integer denotes the corresponding InvisibleObject.

ipObjectSpec('IPClassObject',X,[C],_)

X is the class object for class with name C

ipObjectSpec('IPClassVariable',X,[C,V],_)

X is the class variable V of class C

ipObjectSpec(boolean,X,[B],_)

X is a boolean basic type for B (which should be 1 or 0); similar ipObjectSpec facts are available for the remaining basic types: byte, small, int, long, float, double, char

ipObjectSpec(C,X,Variables,_)

(Generic case) Object X of class C; C must be the fully qualified class name.

Visualization of Prolog data

browseTerm(Term) 

Creates a window with an outline (JTree) browser for Term.

browseList(List) 

Creates a window with a JList browser on List. Double-clicking on items creates a term browser window.

browseTreeTerm(Tree) 

Creates a window with a multi-pane hierarchical browser for Tree; this is assumed to be represented by some (dummy) functor with arity 2 or larger; the first argument is considered the node, the second a children list. The tree must have depth 2 or larger.

browseLiteralInstances(GroundTerm,Instances) 

Creates a window with a JTable showing a set of term instances; the GroundTerm is used to title the table columns.

Caching of Java state

ipInitIsShowing(GUI) starts maintaining a fact ipIsShowing(GUI,true or false), reflecting the state of java.awt.Component.isShowing() as conveyed on the Java side by the ComponentListener interface. This is typically more efficient then repeatedly polling the GUI object.

FAQ

Please see here.

Download and install

Your download tacitly accepts Apache LICENSE 2.0 for these files. Use it strictly at your own risk.

  • For a simple test:
java -jar interprolog.jar PATH_TO_XSB_EXECUTABLE

You should get a Prolog listener window. There's more (ancient) information on the old InterProlog bridge, [site].

Main changes since the last version

  • New java(...) predicates supporting Java type annotations to Prolog parameters, hence simpler to use than javaMessage
  • Now assumes Java 6 or later, and compiles properly under Eclipse
  • includes package com.declarativa.interprolog.remote
  • Preliminary LPS (Logic Programming Systems) Java API, class LPSEngine
  • minor bug fixes
  • introduced a bug in InitiallyFlatTermModel, now seems allergic to longs

Older versions