Previous Up Next

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.


Previous Up Next