// BEGIN LICENSE BLOCK
// Version: CMPL 1.1
//
// The contents of this file are subject to the Cisco-style Mozilla Public
// License Version 1.1 (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License
// at www.eclipse-clp.org/license.
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
// the License for the specific language governing rights and limitations
// under the License.
//
// The Original Code is The ECLiPSe Constraint Logic Programming System.
// The Initial Developer of the Original Code is Cisco Systems, Inc.
// Portions created by the Initial Developer are
// Copyright (C) 2001 - 2006 Cisco Systems, Inc. All Rights Reserved.
//
// Contributor(s): Josh Singer, Parc Technologies
//
// END LICENSE BLOCK
//Title: Java/ECLiPSe interface
//Version: $Id: OutOfProcessEclipse.java,v 1.5 2013/02/10 18:54:45 jschimpf Exp $
//Author: Josh Singer
//Company: Parc Technologies
//Description: ECLiPSe engine run on local machine, separate process
package com.parctechnologies.eclipse;
import java.io.*;
import java.net.*;
/**
* An ECLiPSe engine which runs in a child process of the Java virtual machine.
* An OutOfProcessEclipse is created using the public constructor, which
* takes an EclipseEngineOptions object. A JVM may have any number of
* instances of this class. Invocation of the destroy()
method does
* not affect the ability to create new OutOfProcessEclipse instances.
*/
public class OutOfProcessEclipse implements EclipseConnection, EclipseEngine
{
private Process eclipseProcess;
private RemoteEclipse eclipseConnection;
private BufferedReader eclipseProcessStdout;
private BufferedReader eclipseProcessStderr;
private BufferedWriter eclipseProcessStdin;
private boolean useQueues;
private ToEclipseQueue stdin;
private FromEclipseQueue stdout, stderr;
public synchronized boolean isUsingQueues()
{
return useQueues;
}
/**
* Create a new OutOfProcessEclipse using the supplied options.
*
* @throws IOException if the connection to the child process failed
* @throws EclipseException if there was a problem setting up the ECLiPSe side
* of the connection.
*
* @param options settings for the new ECLiPSe engine.
*/
public OutOfProcessEclipse(EclipseEngineOptions options)
throws IOException, EclipseException
{
startLocalEclipse(options);
// Read the port number from the eclipseProcess' stdout
String portString = eclipseProcessStdout.readLine();
// Read the password from the eclipseProcess' stdout
String passwd = eclipseProcessStdout.readLine();
// make port string into an int
int port = Integer.parseInt(portString);
// connect
remoteConnect(InetAddress.getByName("localhost"), port, passwd);
// set the useQueues flag according to options
useQueues = options.isUsingQueues();
// System.out.println("initial attachment complete");
// connect the local Eclipse's standard streams to queues,
// and redirect these to the System standard streams if
// necessary.
connectStdStreams();
// if not using standard stream queues, need to add listeners to them to
// link up with JVM's standard streams
if(!options.isUsingQueues())
{
stdStreamsAddListeners();
}
}
private void connectStdStreams() throws EclipseException, IOException
{
// For each standard stream, set up a queue
stdout = eclipseConnection.getFromEclipseQueue("joop_stdout");
stderr = eclipseConnection.getFromEclipseQueue("joop_stderr");
stdin = eclipseConnection.getToEclipseQueue("joop_stdin");
// make sure these get flushed eagerly, so users see error messages
eclipseConnection.rpc("set_stream_property(joop_stdout,flush,end_of_line)");
eclipseConnection.rpc("set_stream_property(joop_stderr,flush,end_of_line)");
// now redirect Eclipse streams to write to / read from these queues
// (NOTE: setting user_XXX before stdXXX is the only way to change stdXXX!)
eclipseConnection.rpc("set_stream(user_input, joop_stdin)");
eclipseConnection.rpc("set_stream(stdin, joop_stdin)");
eclipseConnection.rpc("set_stream(user_output, joop_stdout)");
eclipseConnection.rpc("set_stream(stdout, joop_stdout)");
eclipseConnection.rpc("set_stream(user_error, joop_stderr)");
eclipseConnection.rpc("set_stream(stderr, joop_stderr)");
// ... and all the aliases of these basic streams as well
eclipseConnection.rpc("set_stream(output, joop_stdout)");
eclipseConnection.rpc("set_stream(warning_output, joop_stdout)");
eclipseConnection.rpc("set_stream(log_output, joop_stdout)");
eclipseConnection.rpc("set_stream(error, joop_stderr)");
eclipseConnection.rpc("set_stream(input, joop_stdin)");
}
private void stdStreamsAddListeners()
{
try
{stdin.setListener(new StdInListener());}
catch(IOException ioe) // should never be thrown as queue is not closed
{System.err.println("Error: setting of listener threw an IOException.");}
try
{stdout.setListener(new StdOutListener("output"));}
catch(IOException ioe) // should never be thrown as queue is not closed
{System.err.println("Error: setting of listener threw an IOException.");}
try
{stderr.setListener(new StdOutListener("error"));} // no special listener for stderr
catch(IOException ioe) // should never be thrown as queue is not closed
{System.err.println("Error: setting of listener threw an IOException.");}
}
private void remoteConnect(InetAddress hostAddr, int port,
String passwd) throws IOException, EclipseException
{
//System.out.println("Trying to make connection on host "+hostString+", port "
// +port+" password "+passwd);
// try to set up the connection on the specified port
try
{
eclipseConnection =
new RemoteEclipse(hostAddr, port, passwd);
//System.out.println("Connection successful.");
eclipseProcessStdin.write("accept. \n");
eclipseProcessStdin.flush();
//System.out.println("Accept signal written and flushed.");
// signal to the eclipse to resume execution + wait until it relinquishes
// control
eclipseConnection.resume();
}
catch(IOException ioe)
{
// if unsuccessful, signal to the eclipse to shut down before re-throwing
// exception
//System.out.println("Connection unsuccessful."+ioe);
try
{
eclipseProcessStdin.write("reject. \n");
eclipseProcessStdin.flush();
}
catch(Exception e){}
//System.out.println("Reject signal written and flushed.");
throw ioe;
}
}
private void startLocalEclipse(EclipseEngineOptions options)
throws EclipseException, IOException
{
// What to do here varies according to platform
if(!Platform.getInstance().supportsOutOfProcessEclipse())
{
throw
new UnsupportedOperationException("The creation of an OutOfProcessEclipse"+
" is not supported on platform "+ System.getProperty("os.name")+
"/"+ System.getProperty("os.arch") + ".");
}
// The command to start eclipse : varies according to platform + options
String[] cmdarray = new String[100];
// index of next empty string in command array
int cmd = 0;
if(options.getEclipseDir() == null)
{
throw new EclipseException("The location of the ECLiPSe installation was not set in the EclipseEngineOptions object supplied.");
}
File eclipseDir = new File(options.getEclipseDir());
File executableSubdir =
Platform.getInstance().getExecutableSubdirectory(eclipseDir);
File librarySubdir =
Platform.getInstance().getLibrarySubdirectory(eclipseDir);
if (!executableSubdir.equals(librarySubdir) && executableSubdir.exists())
{
// Eclipse has been fully installed, and an 'eclipse' script exists:
// run the script because it sets some potentially useful environment
// variables (e.g. LD_LIBRARY_PATH, JRE_HOME, TCL_LIBRARY)
cmdarray[cmd++] = (new File(executableSubdir, "eclipse")).toString();
}
else
{
// No bin-directory, i.e. we have a non-installed Unix-Eclipse, or we
// are on Windows (possibly without registry entries). We try to run
// eclipse.exe, supplying the installation directory via the command
// line. This should work for many applications, provided things like
// LD_LIBRARY_PATH are taken care of by the host application.
cmdarray[cmd++] = (new File(librarySubdir, "eclipse.exe")).toString();
cmdarray[cmd++] = "-D";
cmdarray[cmd++] = eclipseDir.toString();
}
// if user has set it in options, add a command line option to set up the
// local stack size
if(options.getLocalSize() != 0)
{
cmdarray[cmd++] = "-l";
cmdarray[cmd++] = (new Integer(options.getLocalSize()).toString())+"M";
}
// if user has set it in options, add a command line option to set up the
// global stack size
if(options.getGlobalSize() != 0)
{
cmdarray[cmd++] = "-g";
cmdarray[cmd++] = (new Integer(options.getGlobalSize()).toString())+"M";
}
cmdarray[cmd++] = "-e";
// if user has set default module in options, add goal to do
// this, and then start the joop_boot
if (options.getDefaultModule() != null )
{
cmdarray[cmd++] = "set_flag(toplevel_module, "+
options.getDefaultModule()+"), joop_boot:jb_go";
}
else // Otherwise just add a goal to start the joop_boot
{
cmdarray[cmd++] = "joop_boot:jb_go";
}
// Add bootfile. This is the ECLiPSe code which will set up the remote
// connection etc. It is initialised using the "jb_go" predicate.
cmdarray[cmd++] = "-b";
cmdarray[cmd++] = (new File(new File(eclipseDir, "lib"), "joop_boot.eco")).toString();
// copy to new array which is just big enough
String[] cmdarray2 = new String[cmd];
System.arraycopy(cmdarray, 0, cmdarray2, 0, cmd);
eclipseProcess = Runtime.getRuntime().exec(cmdarray2);
eclipseProcessStdout =
new BufferedReader(new InputStreamReader(eclipseProcess.getInputStream()));
eclipseProcessStderr =
new BufferedReader(new InputStreamReader(eclipseProcess.getErrorStream()));
eclipseProcessStdin =
new BufferedWriter(new OutputStreamWriter(eclipseProcess.getOutputStream()));
// write the peer name specified in options on stdin
eclipseProcessStdin.write(options.getPeerName()+". \n");
eclipseProcessStdin.flush();
}
/**
* Finalizer method called when object is to be garbage collected
*/
protected void finalize() throws IOException, EclipseException
{
this.destroy();
}
public synchronized ToEclipseQueue getEclipseStdin() throws EclipseTerminatedException
{
eclipseConnection.testTerminated();
if(useQueues)
return(stdin);
else
return(null);
}
public synchronized FromEclipseQueue getEclipseStdout() throws EclipseTerminatedException
{
eclipseConnection.testTerminated();
if(useQueues)
return(stdout);
else
return(null);
}
public synchronized FromEclipseQueue getEclipseStderr() throws EclipseTerminatedException
{
eclipseConnection.testTerminated();
if(useQueues)
return(stderr);
else
return(null);
}
/**
* Terminate the OutOfProcessEclipse process and the connection to it.
* After destroy()
has been invoked, future invocations of public
* methods will throw EclipseTerminatedExceptions
*
* @throws EclipseTerminatedException if the destroy()
method had
* already been called.
*/
public void destroy() throws IOException
{
eclipseConnection.testTerminated();
try // multilateral disconnect
{
eclipseConnection.disconnect();
}
catch(Exception e)
{ // if that doesnt work, try unilateral
try
{
eclipseConnection.unilateralDisconnect();
}
catch(EclipseTerminatedException ete)
{}
}
}
public CompoundTerm rpc(String goal) throws EclipseException, IOException
{
return(eclipseConnection.rpc(goal));
}
public CompoundTerm rpc(CompoundTerm goal) throws EclipseException, IOException
{
return(eclipseConnection.rpc(goal));
}
public FromEclipseQueue getFromEclipseQueue(String name) throws EclipseException, IOException
{
return(eclipseConnection.getFromEclipseQueue(name));
}
public ToEclipseQueue getToEclipseQueue(String name) throws EclipseException, IOException
{
return(eclipseConnection.getToEclipseQueue(name));
}
public AsyncEclipseQueue getAsyncEclipseQueue(String name) throws EclipseException, IOException
{
return(eclipseConnection.getAsyncEclipseQueue(name));
}
public void compile(File f) throws EclipseException, IOException
{
eclipseConnection.compile(f);
}
public String getPath(File f) throws EclipseException, IOException
{
return(eclipseConnection.getPath(f));
}
public CompoundTerm rpc(String functor, Object arg1) throws EclipseException, IOException
{
return(eclipseConnection.rpc(functor, arg1));
}
public CompoundTerm rpc(String functor, Object arg1,
Object arg2) throws EclipseException, IOException
{
return(eclipseConnection.rpc(functor, arg1, arg2));
}
public CompoundTerm rpc(String functor, Object arg1,
Object arg2, Object arg3) throws EclipseException, IOException
{
return(eclipseConnection.rpc(functor, arg1, arg2, arg3));
}
public CompoundTerm rpc(String functor, Object arg1,
Object arg2, Object arg3, Object arg4)
throws EclipseException, IOException
{
return(eclipseConnection.rpc(functor, arg1, arg2, arg3, arg4));
}
public CompoundTerm rpc(String functor, Object arg1,
Object arg2, Object arg3, Object arg4,
Object arg5) throws EclipseException, IOException
{
return(eclipseConnection.rpc(functor, arg1, arg2, arg3, arg4, arg5));
}
public CompoundTerm rpc(String functor, Object[] args) throws EclipseException, IOException
{
return(eclipseConnection.rpc(functor, args));
}
public CompoundTerm rpc(Object[] goalTerm) throws EclipseException, IOException
{
return(eclipseConnection.rpc(goalTerm));
}
public Atom getPeerName()
{
return(eclipseConnection.getPeerName());
}
private class StdInListener implements QueueListener
{
// size of byte chunk we attempt to read from stdin when there is a request
private static final int READ_CHUNK_SIZE = 512;
public void dataAvailable(Object source)
{
}
public void dataRequest(Object source)
{
ToEclipseQueue teq = (ToEclipseQueue) source;
byte[] chunk = new byte[READ_CHUNK_SIZE];
int readResult ;
try
{
readResult = System.in.read(chunk);
if(readResult == -1) // EOF
{
// do nothing?
}
else // write the part of chunk to queue
{
teq.write(chunk, 0, readResult);
teq.flush();
}
}
catch(IOException ioe)
{
// write these out to stderr
System.err.println("Attempt to read from JVM's stdin produced exception:\n"+ioe);
System.err.flush();
}
}
}
private class StdOutListener implements QueueListener
{
private String destination;
public StdOutListener(String destination)
{
this.destination = destination;
}
public void dataAvailable(Object source)
{
FromEclipseQueue feq = (FromEclipseQueue) source;
byte[] chunk;
try
{
int availableBytes = feq.available();
chunk = new byte[availableBytes];
feq.read(chunk);
}
catch(IOException ioe)
{
// write these out to stderr
System.err.println("Attempt to use ECLiPSe's output queue produced exception:\n"
+ioe);
System.err.flush();
return;
}
if(destination.equals("output"))
{
System.out.print(new String(chunk));
return;
}
if(destination.equals("error"))
{
System.err.print(new String(chunk));
return;
}
}
public void dataRequest(Object source)
{
}
}
// implements method from EclipseConnection
public EclipseMultitaskConnection registerMultitask(MultitaskListener multitaskListener) throws EclipseException,IOException {
return eclipseConnection.registerMultitask(multitaskListener);
}
}