Prolog Connector

The Prolog Connector establishes the connection of a Java process and a Prolog process and can be used independently from the IDE part (see PDT Architecture).

The Prolog Connector is based on sockets and implements a pure client-server relation: The Java processes send requests, the Prolog processes answer. Communication from Prolog to Java is possible via a notification / observer mechanism: The Java side can declare to be listening for certain events and can explicitly call the Prolog side when notified that the respective events occurred.

If you just want to run Prolog code from Java, without using the developement environment of the PDT, you can use the provided library. All you need is SWI-Prolog (no need for Eclipse or any other libraries).

If you are using Windows, make sure that the “bin” directory of your SWI-Prolog installation is added to the system PATH.

Getting an instance of a PrologProcess

Each instance of a PrologProcess corresponds to one Prolog process. There are two ways to retrieve an instance depending on how you use the connector (library or Eclipse plugin).

Library

If you use the connector library you can get an instance via

org.cs3.prolog.connector.Connector.newPrologProcess()

which uses SWI Prolog from the system PATH or you can use

org.cs3.prolog.connector.Connector.newPrologProcess(executable)

which uses the specified SWI Prolog executable.

Eclipse Plugin

If you want to use the connector within an Eclipse plugin that depends on the PDT we recommend to depend on the org.cs3.pdt.connector plugin and to use

org.cs3.pdt.connector.PDTConnectorPlugin.getDefault().getPrologProcess(key)

to get an instance.

Executing queries and Processing Results

Queries are passed as simple Java Strings to the responsible methods.

One result of a query is a Java Map containing the bindings for all variables of the query. The variables are the keys of the map.
The type of the variable values differ, depending on the given flag. If not specified otherwise the DEFAULT flag will be used. This means, that the values will be instances of String or instances of java.util.List (in case of Prolog lists). The elements in this list can also be of type String or List. This is the recommended way if the results only consist of atoms, numbers and lists or if the string representation of a result is sufficient for the context of the program.

There are two methods that execute a query:

  • org.cs3.prolog.connector.process.PrologProcess.queryOnce(query)

    returns the first result of the query or null if the query fails.

  • org.cs3.prolog.connector.process.PrologProcess.queryAll(query)

    returns all results of the query. If the query fails the returned list will be empty.

Each result is represented as a Map<String, Object>. The String is the name of a variable in the query. The Object is the value of the variable generated by the query. The following listing shows an example.

PrologProcess process = Connector.newPrologProcess();
String query = "father_of(Child, 'Peter')";
 
Map<String, Object> result = process.queryOnce(query);
List<Map<String, Object>> results = process.queryAll(query);

To access the results (that is, the values of the variables from the query) you must know which kind of value to expect:

  • an atom → a String (in the map)
  • a list → a List of Objects (in the map)
  • a function term → a CTerm instance (in the map)

In the above example we expect the name of Peter's father to be an atom (→ String), so we access it via

String firstChildOfPeter = result.get("Child").toString();

Utilities for queries

The class QueryUtils contains several utility methods which are useful for constructing queries.

The most important one is

org.cs3.prolog.connector.common.QueryUtils.buildTerm(functor, args...)

which constructs a Prolog term for a functor and an arbitrary number of arguments. Using this method makes the code more readable and avoids String concatenations, especially when using Java variables in the term construction. There is also the method QueryUtils.bT(functor, args…) that forwards to buildTerm but has a shorter name.

An example is:

String child = "peter";
String query = QueryUtils.buildTerm("father_of", "Father", child);
// this is the same as "father_of(Father, peter)"

Sessions

Sessions are used to execute queries. For each session there is one corresponding thread in the Prolog process and one socket connection between Java and Prolog. There are two kinds of sessions:

Sessions can be obtained by the getSession… methods of a PrologProcess and need to be disposed when they are no longer needed. The query… methods of a PrologProcess create a session automatically and dispose it for each query.

If you want to execute lots of queries in a short time it is recommended to create one session and execute the queries on this session instead of using the query… methods of a PrologProcess. The following listing displays an example.

PrologSession session = null;
try {
    // create a session for the current process
    session = process.getSession();
    // execute a lot of queries in a short period
    for (int i = 0; i < 10000; i++) {
 
        // use the queryOnce method from the session
        // this prevents the creation of a new session for every query
        session.queryOnce(bT("assertz", "p_" + i));
    }
} catch (PrologProcessException e) {
    e.printStackTrace();
} finally {
    // make sure to close the session after the queries are finished
    if (session != null) {
        session.dispose();
    }
}

Listen to events from Prolog

In addition to the message based interaction of Java and Prolog supported by the queryOnce and queryAll methods, the PDT connector also supports an event / observer / notification mechanism.

The Java side does not need to poll the Prolog processes to find out when some event occurred but can declare that it is listening for certain events. The Prolog side triggers these events if something relevant happens (e.g. the end of a long running computation). Then the Java side can send a normal queryOnce / queryAll to retrieve the results.

Create the listener

First you need to create a PrologEventDispatcher for a PrologProcess. Then add a PrologEventListener to this dispatcher. Every listener can be listening to one or more subjects.

currentDispatcher = new PrologEventDispatcher(currentProcess);
currentDispatcher.addPrologEventListener("pdt_edit_hook", this);

The code above (excerpt from org.cs3.pdt.editor.internal.editors.CurrentProcessListener) creates and registers a listener for the pdt_edit_hook event.

When the related event is sent by the Prolog side, the update method of each registered PrologEventListener will be called with a parameter of type PrologEvent that contains the event identifier (pdt_edit_hook in our example) and some arbitrary additional data from the second parameter of the pif_notify/2 call (see below).

Sending Prolog events

To send a notification from a Prolog process, call the pif_notify/2 predicate. Its first argument is an even identifier (which must be an atom) and the second argument an arbitrary value that will be passed to the Java side along with the event.

pif_observe:pif_notify(pdt_edit_hook, 'l:\test.pl 1')

The above tells the Java side that the pdt_edit_hook event occurred for the file 'l:\test.pl'. For an example see the module pdt_editor_edit_hook.

Last modified: 2016/04/17 20:43
*