Java API - overview

Table of Contents

Introduction

The Java package org.jpl7 contains all of the classes in this interface. None of the classes correspond with any of the data types in the Prolog Foreign Language Interface (FLI).

The Class Hierarchy

The API consists of the following class hierarchy:

Term
 |
 +--- Atom
 |
 +--- Compound
 |
 +--- Float
 |
 +--- Integer
 |
 +--- Variable
 |
 +--- JRef
 
Query

JPLException
 |
 +-- PrologException

Term is an abstract class: only its subclasses can be instantiated.

Each instance of Query contains a Term (denoting the goal which is to be proven), and much more besides.

Each instance of Compound has a (java.lang.String) name and an array of (Term) arguments (it must have at least one).

Initializing Prolog

The org.jpl7.JPL class initializes the Prolog VM (e.g. libswipl.dll on Windows), if necessary, when the first Query is activated, using default parameter values. Before initialization takes place, these default values can be read, and altered.

public String[] getDefaultInitArgs();
public void setDefaultInitArgs(String[] args);

After initialization, the parameter values which were actually used can be read.

public String[] getActualInitArgs();

This method returns null if initialization has not occurred, and thus it can be used as a test. This allows Java library classes to employ JPL without placing any burden of initialization upon the applications which use them.  It can also ensure that the Prolog VM is initialized only if and when it is needed.

Explicit initialization is supported as in JPL 1.0.1:

public void init();
public void init(String args[]);

Java code which requires a Prolog VM to be initialized in a particular way can check whether initialization has already occurred: if not, it can specify parameters and force it to be attempted; if so, it can retrieve and check the initialisation parameters actually used, to determine whether the initialization meets its requirements.

This version of JPL does not support reinitialization of a Prolog VM.

For details about the legal parameter values, see your local Prolog documentation.  Most users will rely on automatic initialization.

Creating Terms

The Term-based classes in the org.jpl7 package are a structured concrete syntax for Prolog terms: they are not references to actual terms within the Prolog engine; rather, they are a means for constructing queries which can be called within Prolog, and they are also a means for representing (and exploring) the results of such calls.

Term instances are never changed by any activity within the Prolog engine: indeed; it doesn't know of their existence.

The Term class is abstract, so it cannot be directly instantiated; to create a Term, create an instance of one of its subclasses.

A Term in the org.jpl7 packagee is not to be confused with a term_t in the org.jpl7.fli package. The latter has an important internal role in managing state in the Prolog stack; the former is just a data structure in the Java heap.

Atoms

An Atom represents a Prolog text atom. To create an Atom, pass a (String) name to its constructor:

Atom aristotle = new Atom("aristotle");
Atom alexander = new Atom("alexander");

Two Atoms by the same name are effectively identical. Feel free to reuse Atom instances when constructing compound Terms.

The name in an Atom need not be lower case: it can be any UTF-8 string.

An Atom's name is retrieved with its name() method, e.g.

aristotle.name()

Variables

Variables have identifying names, which must comply with conventional Prolog source text syntax.

Variable V1 = new Variable("X"); // a regular variable

Variable V2 = new Variable("_"); // an "anonymous" variable

Variable V3 = new Variable("_Y"); // a "dont-tell-me" variable, whose bindings we don't want to know

Integers

A org.jpl7.Integer is a specialized org.jpl7.Term which holds a Java long value or a java.math.BigInteger object. This class corresponds to the Prolog integer type.

org.jpl7.Integer i = new org.jpl7.Integer(5);

Be careful to avoid confusion with java.lang.Integer, e.g. by always qualifying the class name as in the example above.

The org.jpl7.Integer class has an intValue() accessor to obtain the int value of an instance, and also longValue(), floatValue() and doubleValue() (just like java.lang.Integer has).

If isBig() returns true, then the value is outside the range of a Java long, and is retrieved by bigValue().

Floats

A org.jpl7.Float is a specialized org.jpl7.Term which holds a Java double value. This class corresponds to the Prolog float type (64-bit ISO/IEC in SWI Prolog).

org.jpl7.Float f = new org.jpl7.Float(3.14159265);

As with integers, take care to avoid confusion between org.jpl7.Float and java.lang.Float.

The org.jpl7.Float class has a doubleValue() accessor to obtain the double value of an instance, and also a floatValue() accessor.

Compounds

A org.jpl7.Compound is a specialized org.jpl7.Term which contains a name and an array of org.jpl7.Term arguments, and can be constructed e.g.

Compound teacher_of = new Compound(
    "teacher_of",
    new Term[] {
        new Atom("aristotle"),
        new Atom("alexander")
    }
);

Note the use of Java's anonymous array syntax

new Term[] { ..., ... }

to specify any quantity of arguments.

In this example, the Java variable teacher_of refers to a Compound instance, which represents the Prolog term teacher_of(aristotle,alexander).

Care should be taken in creating Compound Terms, especially if Variable references are used. For example, the following construction:

Variable X = new Variable();
Variable Y = new Variable();
Compound father_of = new Compound("teacher_of", new Term[]{X,Y});

corresponds with the Prolog term teacher_of(X,Y), whereas

Variable X = new Variable();
Compound father_of = new Compound("teacher_of", new Term[]{X,X});

corresponds with the Prolog term teacher_of(X,X), two terms that can resolve very differently depending on the Prolog database. The general rule of thumb should be, reuse Term references that are or contain Variables only if you know that that is what you mean.

To obtain the (String) name of a Compound, use the name() accessor method.

public String name();

To obtain the arity of a Compound, use the arity() accessor method.

public int arity();

To obtain an array of a Compound's arguments, use the args() accessor method.

public Term[] args();

To obtain the ith argument of a compound (numbered from 1), use the arg() accessor method (with an int parameter value between 1 and Arity inclusive).

public Term arg(int i);

To obtain the ith argument of a compound (numbered from 0), use the arg0() accessor method (with an int parameter value between 0 and Arity-1 inclusive).

public Term arg0(int i);

Queries

A Query contains a Term, representing a Prolog goal:

Term goal = new Compound("teacher_of", new Term[]{new Atom("aristotle"),new Atom("alexander")});
Query q = new Query(goal );

The Query q in this example represents the Prolog query

?- teacher_of(aristotle,alexander).

The Util Class

The Util class provides various static utility methods for managing JPL Terms.

Term termArrayToList(Term t[])
Term[] listToTermArray(Term t)
Term[] bindingsToTermArray(Hashtable bs)

Querying Prolog

To ask the Prolog engine a query via the High-Level Interface, one first constructs a Query instance, as in the above example, and then uses the java.util.Enumeration interface, which the Query class implements, to obtain solutions (where a "solution" is what is known in logic programming jargon as a substitution, which is a collection of bindings, each of which relates one of the Variables within the Query's goal to a Term representation of the Prolog term to which the corresponding Prolog variable was bound by the proof).

public interface Enumeration {
    public boolean hasMoreElements();
    public Object nextElement();
}

The hasMoreElements() method can be used to determine whether a Query has any (or any further) solutions. In the above example, the method call

q.hasMoreElements()

returns true if the Prolog query teaches(aristotle,alexander) is provable, and false otherwise. In this example, the Prolog query is a ground term, so the "solution" to the Query is merely a truth value, and is given by the hasMoreElements() method.

Where a Query's goal contains Variables, on the other hand, its execution yields a sequence of bindings of these Variables to Terms. The High-Level interface uses a java.util.Hashtable to represent these bindings; the Objects in the table are Terms, keyed (uniquely) by Variable instances.

For example, to print all of Aristotle's pupils, i.e., all the bindings of X which satisfy teaches(aristotle,X), one could write

Variable X = new Variable();
Query q = new Query("teaches", new Term[] {new Atom("aristotle"),X});
while (q.hasMoreElements()) {
    Hashtable binding = (Hashtable) q.nextElement();
    Term t = (Term) binding.get(X);
    System.out.println(t);
}

If a Query's goal contains no variables (i.e. it is "ground"), the Query. nextElement() method will still return a Hashtable for each solution, although each table will be empty.

If a Query's goal contains more than one occurrence of some Variable, then each solution Hashtable will have only one binding for that Variable.

For convenience, the Query class provides a hasMoreSolutions() and nextSolution() method with the following signatures:

public boolean hasMoreSolutions();
public Hashtable nextSolution();

Using the nextSolution() method avoids having to cast the result of the nextElement() method to Hashtable.

Obtaining one Solution

Often, you'll just want just the first solution to a query. The Query class provides a method for this:

public Hashtable oneSolution();

If the Query has no solutions, this method returns null; otherwise, a non-null return indicates success. If the Query is a ground query (i.e. contains no variables), the returned Hashtable will be empty (i.e. will contain no bindings).

Obtaining all Solutions

You may want all solutions to a query. The Query class provides a method for this:

public Hashtable[] allSolutions();

The returned array will contain all the Query's solutions, in the order they were obtained (as with Prolog's findall/3, duplicates are not removed). If the Query has no solutions, this method returns an empty array (N.B. not null as in JPL 1.0.1).

Discovering whether a query has any solutions

Sometimes an application is interested only in whether or not a query is provable, but not in any details of its possible solutions. The Query class provides the hasSolution method for this common special case:

public boolean hasSolution();

This method is equivalent to (but sometimes more efficient than) calling oneSolution and asking whether the return value is non-null (i.e. whether the query succeeded).

Terminating Queries

Queries terminate automatically when the hasMoreSolutions() method returns false, and once a Query is terminated, another can be started. Unfortunately, the Prolog engine is currently such that it can handle only one query at a time. As a result, it is not possible, in the High-Level Interface, to ask two different Query objects whether they have any solutions without first exhausting all of the solutions of one. Therefore, programmers must take care to ensure that all solutions are exhausted before starting new queries. This has particular importance in multi-threaded contexts, but it can also present difficulties even in single-threaded programs. See the Multi-Threaded Queries section for a discussion of how to manage Queries in multi-threaded contexts.

To terminate a Query before all of its solutions have been exhausted, use the rewind() method:

public void rewind();

This method stops a Query, setting it back into a state where it can be restarted. It also permits other queries to be started. Here is an example in which the first three solutions to the Query are obtained:

Query query = // obtain Query somehow
for (int i = 0; i < 3 && query.hasMoreSolutions(); ++i ){
    Hashtable solution = query.nextSolution();
    // process solution...
    }
query.rewind();

You may call rewind() on an inactive Query without ill-effect, and you should always call rewind if you have not exhausted all solutions to a Query.

If you are using the query(), oneSolution(), or allSolutions() methods, you need not worry about rewinding the Query; it is done automatically for you.

Multi-Threaded Queries

The Prolog engine can only have one query open at a time. This presents difficulties for multi-threaded programs in which the programmer has no control over when Queries are executed. JPL makes as much of the High-Level Interface thread-safe as it can. Unfortunately, the programmer must take responsibility in a limited set of circumstances to ensure that all calls to the High-Level Interface are thread safe.

It is worth noting that if the programmer confines use of Query methods to hasSolution(), oneSolution(), and allSolutions(), that subset of the Query interface is thread-safe. For many programmers, these methods suffice. However, if the hasMoreSolutions(), hasMoreElements(), nextSolution(), nextElement(), or rewind() methods are explicitly invoked, thread-safety is lost. The problem is that while the blocks of these programs are synchronized so that in effect no two Query objects can invoke any of these methods concurrently, there is nothing that prevents a Query object in one thread from calling one of these methods, and another Query object in a different thread from calling this same method, or even another that could produce indeterminate results.

The Query class, however, does make synchronization around these methods possible by providing a reference to the monitor object that locks competing threads from executing critical code. The reference is obtained by the static method

public static Object lock();

Thus, programmers can wrap calls to these non-thread-safe methods in synchronized blocks, using the lock object to prevent other threads from entering any of these methods. To write a thread-safe loop to process all of a Query's solutions, for example, one might write

Query query = // obtain Query
synchronized (Query.lock() ){
while (query.hasMoreSolutions() ){
Hashtable solution = query.nextSolution();
// process solution...
}
}


Note that the query(), oneSolution(), and allSolutions() methods effectively do the same as the above code snippet, so there is no need to explicitly synchronized on the Query's monitor object when these methods are called.

Exceptions

The JPL package provides fairly crude exception handling. The base class for all JPL Exceptions is JPLException, which is a java.lang.RuntimeException (and hence need not be declared), and which will be thrown in the absence of any other kind of exception that can be thrown, usually as the result of some programming error. Converting the exception to a java.lang.String should provide some descriptive information about the reason for the error. All other JPL excpetion classes extend this class. Currently there are two, the QueryInProgressException class and the PrologException class.

A QueryInProgressException is thrown when a Query is opened while another is in progress; this exception can be caught in multi-threaded situations, but a better strategy for managing multi-threaded situations is discussed in the Multi-Threaded Queries section. If you obey the rules discussed in this section, you should have no reason to catch this exception.

A PrologException is thrown either during execution of a Prolog built-in predicate or by an explicit call, by Prolog application code, of the Prolog predicate throw/1.

There is currently no means of gracefully handling exceptions caused by malformed parameters (e.g., undefined predicates) passed through the High-Level Interface to the Prolog engine (?).

Debugging

Each Term type (together with the Query class) supports an implementation of toString() which returns a more-or-less familiar Prolog textual representation of the Term or Query.

Sometimes, however, this information is not sufficient, so we have provided a method debugString() which provides a more verbose and explicit representation, including the types (atom, integer etc) of each term and subterm.

In general, Term and Query instances are represented in the form (type data), where type is the name of the type (e.g., Atom, Compound, Tuple, etc.), and data is a representation of the contents of the Term. For example, if the Term is an Atom, the data is the Atom's name. The arguments of Compounds are represented by comma-separated lists within square brackets ('[' ']').

Viewing the structure of a Term or Query can be useful in determining whether an error lies on the Prolog or Java side of your JPL applications.

Perhaps better still, Term implements (in a basic but adequate way) the javax.swing.TreeModel interface, and its display() method creates a JFrame containing a browseable JTree representation of the term.

Version information

To obtain the current version of JPL you are using, you may obtain a reference to the jpl.Version static instance of the JPL class by calling the JPL.version() static method. This will return a jpl.Version structure, which has the following final fields:

package jpl;
public class Version {
    public final int major;                // e.g. 2
    public final int minor;                // e.g. 0
    public final int patch;                // e.g. 2
    public final java.lang.String status;  // e.g. "alpha"
}

You may wish to use this class instance to obtain fine-grained information about the current JPL version, e.g.

if (JPL.version().major == 2) {

You may also simply call the version_string() static method of the jpl.JPL class. This will return a java.lang.String representation of the current JPL version.

The version string can be written to the standard output stream by running the main() method of the jpl.JPL class.

linux% java jpl.JPL
JPL 2.0.2-alpha

Gotchas

arg indexing

The Term[] args of a Compound are indexed (like all Java arrays) from zero, whereas in Prolog the args of a structure are conventionally numbered from one.

representing @(null)

there is no jpl.JNull class: instead, use new JRef(null) to represent @(null) (which itself represents Java's null). If you don't know what this all means, don't worry: it only affects those writing hybrid Java+Prolog programs which call each other nestedly.

all solutions of a Query with no solutions

Query.allSolutions() now returns an empty array of Map if the Query has no solutions (in 1.x versions it inconsistently returned null).

What's Missing

The current implementation of the High-Level Interface lacks support for modules, and for multiple Prolog engines.


up prev next API