8.5 Communicating between Java and ECLiPSe using queues
In the Java-ECLiPSe Interface, queues are one-way data
streams used for communication between ECLiPSe and Java. These are
represented on the ECLiPSe side using “peer queues”, which are
I/O streams. The Java-ECLiPSe Interface includes the classes FromEclipseQueue and ToEclipseQueue which represent these
queues on the Java side. FromEclipseQueue represents a queue
which can be written to in ECLiPSe and read from in Java. A ToEclipseQueue is a queue which can be written to in Java and read
from in ECLiPSe.
Section 8.5.1 discusses how queues are opened,
referenced and closed from either the Java
or ECLiPSe sides. We also discuss here how to transfer byte data in both
directions. However, the programmer need not be concerned with low-level
data operations on queues: whole terms can be written and read using the
EXDRInputStream and EXDROutputStream classes discussed in
Section
8.5.2.
Via the QueueListener feature, Java
code can be invoked (in a sense) from within ECLiPSe. The use of this
feature is discussed in Section 8.5.3. In some
cases, the standard streams (stdin, stdout and stderr) of
the ECLiPSe engine will be visible to Java as queues. How to use these
is discussed in Section 8.5.4.
8.5.1 Opening, using and closing queues
We now explain the standard sequence of events for using
queues. Opening and closing, can be performed in a single step from
either the Java or the ECLiPSe side.
Opening a queue using Java methods
FromEclipseQueue and ToEclipseQueue do not have public
constructors. Instead, we invoke getFromEclipseQueue or getToEclipseQueue. This asks the EclipseConnection object for a
reference to a FromEclipseQueue or ToEclipseQueue instance
which represents a new queue. To specify the stream for later
reference, we supply the method with a string which will equal to the
atom by which the queue is referred to as a stream in ECLiPSe. For
example the following code creates two queues, one in each direction:
...
ToEclipseQueue java_to_eclipse =
eclipse.getToEclipseQueue("java_to_eclipse");
FromEclipseQueue eclipse_to_java =
eclipse.getFromEclipseQueue("eclipse_to_java");
...
These methods will create and return new FromEclipseQueue or
ToEclipseQueue objects, and will also open streams with the
specified names on the ECLiPSe side. No stream in ECLiPSe should
exist with the specified name. If a stream exists which has this name
and is not a queue between the Java object and ECLiPSe, the Java
method throws an exception. If the name is used by a pre-existing
queue, it is returned, so the getFromEclipseQueue and getToEclipseQueue methods can also be used to retrieve the queue
objects by name once if they have already been created.
Opening a queue using ECLiPSe predicates
You can use the ECLiPSe builtin peer_queue_create/5 to open a
queue. Used correctly, these have the same effect as the Java methods
explained above. For the peer name, you should use the atom returned
by the getPeerName() method of the relevant EclipseConnection instance. The direction should be fromec for
a FromEclipseQueue and toec for a ToEclipseQueue. The queue type should always be sync
(for asynchronous queues, refer to section ??).
Transferring data using Java methods
On the Java side, once a FromEclipseQueue has been established,
you can treat it as you would any instance of java.io.InputStream, of which FromEclipseQueue is a
subclass. Similarly, ToEclipseQueue is a subclass of java.io.OutputStream. The only visible difference is that FromEclipseQueue and ToEclipseQueue instances may have QueueListeners attached, as is discussed in Section
8.5.3.
Transferring data using ECLiPSe predicates
On the ECLiPSe side, there are built-in predicates for writing to,
reading from and otherwise interacting with streams. Any of these may
be used. Perhaps most useful are read_exdr/2 and write_exdr/2; these are explained in Section
8.5.2. For the stream ID, you may either use the stream name, or the stream number, obtained for example using peer_get_property/3.
Note: always flush
When communicating between Java and ECLiPSe using queues, you
should always invoke the flush() method of the Java OutputStream which you have written to, whether it be a ToEclipseQueue or an EXDROutputStream. Similarly, on the
ECLiPSe side, flush/1 should always be executed after
writing. Although in some cases reading of the data is possible
without a flush, flushing guarantees the transfer of data.
Closing a queue using Java methods
This is done simply by calling the close() method on the FromEclipseQueue or ToEclipseQueue instance.
Closing a queue using ECLiPSe predicates
This is done by executing the builtin peer_queue_close/1. Note
that the builtin close/1 should not be used in this situation,
as it will not terminate the Java end of the queue.
8.5.2 Writing and reading ECLiPSe terms on queues
Rather than dealing with low-level data I/O instructions such as reading
and writing bytes, the Java-ECLiPSe Interface provides classes for
reading and writing whole terms. In the underlying implementation of these
classes, the EXDR (ECLiPSe eXternal Data Representation) format is
used. This allows ECLiPSe to communicate with other
languages using a common data type. However, it is not necessary for the
API user to know about EXDR in detail to use the Java-ECLiPSe Interface
features discussed in this section.
EXDRInputStream is a subclass of java.io.DataInputStream which can read EXDR format. EXDROutputStream is a subclass of java.io.FilterOutputStream which can write EXDR format.
Initialising EXDRInputStream and EXDROutputStream
The constructor for EXDRInputStream takes an instance of java.io.InputStream as a parameter. This parameter stream is the
source of the EXDR data for the new stream. If data has been written
to the InputStream in EXDR format, you can access it by invoking
the readTerm method of the new EXDRInputStream. This
will read the data from the InputStream and translate the EXDR
format into the Java representation of the data, which is then
returned by readTerm.
Similarly, the constructor for EXDROutputStream takes an
instance of java.io.OutputStream as a parameter. This parameter
stream is the destination of the data written to the new stream. You
write data by invoking the write method of the stream. The
parameter of this method is a Java object representing the piece of
data to be written. The class of this object can be any of the Java
classes mentioned in Table 8.1. The object gets
translated into EXDR format and this is written to the destination
OutputStream.
EXDRInputStream and EXDROutputStream at work
Although the underlying stream could be any kind of stream (e.g. a
file stream), the most common use of EXDRInputStream and EXDROutputStream is to read data from and write data to queues in
EXDR format. In other words, we usually wrap these classes around FromEclipseQueue and ToEclipseQueue classes. We now look at an
example which does just this.
The example is in these two files:
<eclipse_dir>/doc/examples/JavaInterface/QueueExample1.java
<eclipse_dir>/doc/examples/JavaInterface/queue_example_1.pl
The Java program's first relevant action is to invoke the compile method of the EclipseEngine. This causes the ECLiPSe
program to be loaded by ECLiPSe engine. After compile
completes, the Java program creates a ToEclipseQueue and a FromEclipseQueue, with the following lines:
// Create the two queues
java_to_eclipse = eclipse.getToEclipseQueue("java_to_eclipse");
eclipse_to_java = eclipse.getFromEclipseQueue("eclipse_to_java");
Then in the next two lines we create an EXDROutputStream to
format data going to java_to_eclipse and an EXDRInputStream to format data coming from eclipse_to_java.
// Set up the two formatting streams
java_to_eclipse_formatted = new EXDROutputStream(java_to_eclipse);
eclipse_to_java_formatted = new EXDRInputStream(eclipse_to_java);
The Java program writes two atoms to java_to_eclipse_formatted, and then flushes the stream. This
causes each atom to be translated into EXDR format and the translation
to then be written on to java_to_eclipse. The Java program then
makes an rpc invocation to the ECLiPSe program's only predicate
read_2_write_1/0, which is defined as follows:
read_2_write_1:-
read_exdr(java_to_eclipse, Term1),
read_exdr(java_to_eclipse, Term2),
write_exdr(eclipse_to_java, pair(Term1, Term2)),
flush(eclipse_to_java).
The built-in read_exdr/2 reads a term's worth of data from the
stream supplied and instantiates it to the second argument. So read_2_write_1/0 reads the two terms from the stream. They are
then written on to the eclipse_to_java stream within a pair(...) functor using the built-in write_exdr/2, and the
stream is flushed. When the predicate succeeds, the rpc invocation
returns and the term data is on eclipse_to_java in EXDR
format. The next step of the java program is the following:
System.out.println(eclipse_to_java_formatted.readTerm());
Since eclipse_to_java was the FromEclipseQueue passed as a
parameter when eclipse_to_java_formatted was initialised, the
readTerm method of this object reads the EXDR data which is on
eclipse_to_java and converts it into the appropriate Object to
represent the piece of data, in this case a CompoundTerm. This Object is
then returned by readTerm. Hence the output of the program is
pair(a,b).
8.5.3 Using the QueueListener interface
It may sometimes be useful to have Java react automatically to data
arriving on a queue from ECLiPSe. An example of this would
be where a Java program has a graphical display monitoring the state
of search in ECLiPSe. We would like ECLiPSe to be able to send a
message along a queue every time an element of the search state
updates, and have Java react with some appropriate graphical action
according to the message.
Similarly, ECLiPSe may require information from a Java database at
some point during its operation. Again we could use a queue to
transfer this information. If ECLiPSe tries to read from this queue
when it is empty, we would like Java to step in and supply the next
piece of data.
The QueueListener interface is the means by which handlers are
attached to queues on the Java side so that Java reacts
automatically to ECLiPSe's interaction with the queue.
Any object which implements the QueueListener interface can be
attached to either a FromEclipseQueue or a ToEclipseQueue, using
the setListener method. The QueueListener can be removed
using removeListener. Queues can only have one Java
listener at any one time.
The QueueListener interface has two methods: dataAvailable
and dataRequest.
-
dataAvailable
- is invoked only if the QueueListener is
attached to a FromEclipseQueue. It is invoked when the queue is
flushed on the ECLiPSe side.
- dataRequest
- is invoked only if the QueueListener is
attached to a ToEclipseQueue. It is invoked when ECLiPSe tries
to read from the queue when it is empty1.
Both methods have a single Object parameter named source. When they are invoked this parameter is the FromEclipseQueue
or ToEclipseQueue on which the flush or read happened.
There is an example Java program QueueExample2.java with an
accompanying example ECLiPSe program queue_example_2.pl
which use QueueListeners attached to queues going in both
directions.
<eclipse_dir>/doc/examples/JavaInterface/QueueExample2.java
<eclipse_dir>/doc/examples/JavaInterface/queue_example_2.pl
After the queues streams are set up on both sides, the Java program
attaches as listeners a TermProducer to the ToEclipseQueue
and a TermConsumer to the FromEclipseQueue. These are both
locally defined classes which implement QueueListener. The TermProducer, each time its dataRequest method is invoked, sends
one of five different atoms down its queue in EXDR format. The
TermConsumer, when its dataAvailable method is invoked,
reads some EXDR data from its queue and translates it into the
appropriate Java object. It then writes this object out to stdout.
Next, the Java program, using rpc, executes the only predicate
in the ECLiPSe program: read_5_write_5/0. This repeats the
following operation five times: read in a term in EXDR format from the
relevant incoming stream, write it out in EXDR format with an
extra functor to the relevant outgoing stream, and flush the
outgoing stream.
8.5.4 Access to ECLiPSe's standard streams
If the object representing the ECLiPSe implements the EclipseEngine interface, then the API user
may have access to the ECLiPSe's standard streams (see Section
8.7.2). These are returned as FromEclipseQueues and ToEclipseQueues by the methods getEclipseStdin, getEclipseStdout and getEclipseStderr.