1// BEGIN LICENSE BLOCK 2// Version: CMPL 1.1 3// 4// The contents of this file are subject to the Cisco-style Mozilla Public 5// License Version 1.1 (the "License"); you may not use this file except 6// in compliance with the License. You may obtain a copy of the License 7// at www.eclipse-clp.org/license. 8// 9// Software distributed under the License is distributed on an "AS IS" 10// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 11// the License for the specific language governing rights and limitations 12// under the License. 13// 14// The Original Code is The ECLiPSe Constraint Logic Programming System. 15// The Initial Developer of the Original Code is Cisco Systems, Inc. 16// Portions created by the Initial Developer are 17// Copyright (C) 2001 - 2006 Cisco Systems, Inc. All Rights Reserved. 18// 19// Contributor(s): Josh Singer, Parc Technologies 20// 21// END LICENSE BLOCK 22 23//Title: Java/ECLiPSe interface 24//Version: $Id: OutOfProcessEclipse.java,v 1.5 2013/02/10 18:54:45 jschimpf Exp $ 25//Author: Josh Singer 26//Company: Parc Technologies 27//Description: ECLiPSe engine run on local machine, separate process 28package com.parctechnologies.eclipse; 29import java.io.*; 30import java.net.*; 31 32/** 33 * An ECLiPSe engine which runs in a child process of the Java virtual machine. 34 * An <i>OutOfProcessEclipse</i> is created using the public constructor, which 35 * takes an <i>EclipseEngineOptions</i> object. A JVM may have any number of 36 * instances of this class. Invocation of the <code>destroy()</code> method does 37 * not affect the ability to create new <i>OutOfProcessEclipse</i> instances. 38 */ 39public class OutOfProcessEclipse implements EclipseConnection, EclipseEngine 40{ 41 private Process eclipseProcess; 42 43 private RemoteEclipse eclipseConnection; 44 45 private BufferedReader eclipseProcessStdout; 46 47 private BufferedReader eclipseProcessStderr; 48 49 private BufferedWriter eclipseProcessStdin; 50 51 private boolean useQueues; 52 53 private ToEclipseQueue stdin; 54 private FromEclipseQueue stdout, stderr; 55 56 public synchronized boolean isUsingQueues() 57 { 58 return useQueues; 59 } 60 61 /** 62 * Create a new OutOfProcessEclipse using the supplied options. 63 * 64 * @throws IOException if the connection to the child process failed 65 * @throws EclipseException if there was a problem setting up the ECLiPSe side 66 * of the connection. 67 * 68 * @param options settings for the new ECLiPSe engine. 69 */ 70 public OutOfProcessEclipse(EclipseEngineOptions options) 71 throws IOException, EclipseException 72 { 73 startLocalEclipse(options); 74 75 // Read the port number from the eclipseProcess' stdout 76 String portString = eclipseProcessStdout.readLine(); 77 78 // Read the password from the eclipseProcess' stdout 79 String passwd = eclipseProcessStdout.readLine(); 80 81 // make port string into an int 82 int port = Integer.parseInt(portString); 83 84 // connect 85 remoteConnect(InetAddress.getByName("localhost"), port, passwd); 86 87 // set the useQueues flag according to options 88 useQueues = options.isUsingQueues(); 89 90 // System.out.println("initial attachment complete"); 91 92 // connect the local Eclipse's standard streams to queues, 93 // and redirect these to the System standard streams if 94 // necessary. 95 connectStdStreams(); 96 97 // if not using standard stream queues, need to add listeners to them to 98 // link up with JVM's standard streams 99 if(!options.isUsingQueues()) 100 { 101 stdStreamsAddListeners(); 102 } 103 104 105 } 106 107 private void connectStdStreams() throws EclipseException, IOException 108 { 109 // For each standard stream, set up a queue 110 stdout = eclipseConnection.getFromEclipseQueue("joop_stdout"); 111 stderr = eclipseConnection.getFromEclipseQueue("joop_stderr"); 112 stdin = eclipseConnection.getToEclipseQueue("joop_stdin"); 113 114 // make sure these get flushed eagerly, so users see error messages 115 eclipseConnection.rpc("set_stream_property(joop_stdout,flush,end_of_line)"); 116 eclipseConnection.rpc("set_stream_property(joop_stderr,flush,end_of_line)"); 117 118 // now redirect Eclipse streams to write to / read from these queues 119 // (NOTE: setting user_XXX before stdXXX is the only way to change stdXXX!) 120 eclipseConnection.rpc("set_stream(user_input, joop_stdin)"); 121 eclipseConnection.rpc("set_stream(stdin, joop_stdin)"); 122 eclipseConnection.rpc("set_stream(user_output, joop_stdout)"); 123 eclipseConnection.rpc("set_stream(stdout, joop_stdout)"); 124 eclipseConnection.rpc("set_stream(user_error, joop_stderr)"); 125 eclipseConnection.rpc("set_stream(stderr, joop_stderr)"); 126 // ... and all the aliases of these basic streams as well 127 eclipseConnection.rpc("set_stream(output, joop_stdout)"); 128 eclipseConnection.rpc("set_stream(warning_output, joop_stdout)"); 129 eclipseConnection.rpc("set_stream(log_output, joop_stdout)"); 130 eclipseConnection.rpc("set_stream(error, joop_stderr)"); 131 eclipseConnection.rpc("set_stream(input, joop_stdin)"); 132 } 133 134 private void stdStreamsAddListeners() 135 { 136 try 137 {stdin.setListener(new StdInListener());} 138 catch(IOException ioe) // should never be thrown as queue is not closed 139 {System.err.println("Error: setting of listener threw an IOException.");} 140 try 141 {stdout.setListener(new StdOutListener("output"));} 142 catch(IOException ioe) // should never be thrown as queue is not closed 143 {System.err.println("Error: setting of listener threw an IOException.");} 144 try 145 {stderr.setListener(new StdOutListener("error"));} // no special listener for stderr 146 catch(IOException ioe) // should never be thrown as queue is not closed 147 {System.err.println("Error: setting of listener threw an IOException.");} 148 } 149 150 private void remoteConnect(InetAddress hostAddr, int port, 151 String passwd) throws IOException, EclipseException 152 { 153 154 //System.out.println("Trying to make connection on host "+hostString+", port " 155 // +port+" password "+passwd); 156 157 158 159 // try to set up the connection on the specified port 160 try 161 { 162 eclipseConnection = 163 new RemoteEclipse(hostAddr, port, passwd); 164 165 166 //System.out.println("Connection successful."); 167 168 eclipseProcessStdin.write("accept. \n"); 169 eclipseProcessStdin.flush(); 170 171 //System.out.println("Accept signal written and flushed."); 172 173 // signal to the eclipse to resume execution + wait until it relinquishes 174 // control 175 eclipseConnection.resume(); 176 } 177 catch(IOException ioe) 178 { 179 // if unsuccessful, signal to the eclipse to shut down before re-throwing 180 // exception 181 //System.out.println("Connection unsuccessful."+ioe); 182 183 try 184 { 185 eclipseProcessStdin.write("reject. \n"); 186 eclipseProcessStdin.flush(); 187 } 188 catch(Exception e){} 189 //System.out.println("Reject signal written and flushed."); 190 191 throw ioe; 192 } 193 194 195 } 196 197 198 private void startLocalEclipse(EclipseEngineOptions options) 199 throws EclipseException, IOException 200 { 201 // What to do here varies according to platform 202 if(!Platform.getInstance().supportsOutOfProcessEclipse()) 203 { 204 throw 205 new UnsupportedOperationException("The creation of an OutOfProcessEclipse"+ 206 " is not supported on platform "+ System.getProperty("os.name")+ 207 "/"+ System.getProperty("os.arch") + "."); 208 } 209 210 // The command to start eclipse : varies according to platform + options 211 String[] cmdarray = new String[100]; 212 213 // index of next empty string in command array 214 int cmd = 0; 215 216 if(options.getEclipseDir() == null) 217 { 218 throw new EclipseException("The location of the ECLiPSe installation was not set in the EclipseEngineOptions object supplied."); 219 } 220 File eclipseDir = new File(options.getEclipseDir()); 221 File executableSubdir = 222 Platform.getInstance().getExecutableSubdirectory(eclipseDir); 223 File librarySubdir = 224 Platform.getInstance().getLibrarySubdirectory(eclipseDir); 225 226 if (!executableSubdir.equals(librarySubdir) && executableSubdir.exists()) 227 { 228 // Eclipse has been fully installed, and an 'eclipse' script exists: 229 // run the script because it sets some potentially useful environment 230 // variables (e.g. LD_LIBRARY_PATH, JRE_HOME, TCL_LIBRARY) 231 cmdarray[cmd++] = (new File(executableSubdir, "eclipse")).toString(); 232 } 233 else 234 { 235 // No bin-directory, i.e. we have a non-installed Unix-Eclipse, or we 236 // are on Windows (possibly without registry entries). We try to run 237 // eclipse.exe, supplying the installation directory via the command 238 // line. This should work for many applications, provided things like 239 // LD_LIBRARY_PATH are taken care of by the host application. 240 cmdarray[cmd++] = (new File(librarySubdir, "eclipse.exe")).toString(); 241 242 cmdarray[cmd++] = "-D"; 243 cmdarray[cmd++] = eclipseDir.toString(); 244 } 245 246 // if user has set it in options, add a command line option to set up the 247 // local stack size 248 if(options.getLocalSize() != 0) 249 { 250 cmdarray[cmd++] = "-l"; 251 cmdarray[cmd++] = (new Integer(options.getLocalSize()).toString())+"M"; 252 } 253 254 // if user has set it in options, add a command line option to set up the 255 // global stack size 256 if(options.getGlobalSize() != 0) 257 { 258 cmdarray[cmd++] = "-g"; 259 cmdarray[cmd++] = (new Integer(options.getGlobalSize()).toString())+"M"; 260 } 261 262 cmdarray[cmd++] = "-e"; 263 // if user has set default module in options, add goal to do 264 // this, and then start the joop_boot 265 if (options.getDefaultModule() != null ) 266 { 267 cmdarray[cmd++] = "set_flag(toplevel_module, "+ 268 options.getDefaultModule()+"), joop_boot:jb_go"; 269 } 270 else // Otherwise just add a goal to start the joop_boot 271 { 272 cmdarray[cmd++] = "joop_boot:jb_go"; 273 } 274 275 // Add bootfile. This is the ECLiPSe code which will set up the remote 276 // connection etc. It is initialised using the "jb_go" predicate. 277 cmdarray[cmd++] = "-b"; 278 279 cmdarray[cmd++] = (new File(new File(eclipseDir, "lib"), "joop_boot.eco")).toString(); 280 281 // copy to new array which is just big enough 282 String[] cmdarray2 = new String[cmd]; 283 System.arraycopy(cmdarray, 0, cmdarray2, 0, cmd); 284 285 eclipseProcess = Runtime.getRuntime().exec(cmdarray2); 286 287 eclipseProcessStdout = 288 new BufferedReader(new InputStreamReader(eclipseProcess.getInputStream())); 289 290 eclipseProcessStderr = 291 new BufferedReader(new InputStreamReader(eclipseProcess.getErrorStream())); 292 293 eclipseProcessStdin = 294 new BufferedWriter(new OutputStreamWriter(eclipseProcess.getOutputStream())); 295 296 // write the peer name specified in options on stdin 297 eclipseProcessStdin.write(options.getPeerName()+". \n"); 298 eclipseProcessStdin.flush(); 299 } 300 301 302 /** 303 * Finalizer method called when object is to be garbage collected 304 */ 305 protected void finalize() throws IOException, EclipseException 306 { 307 this.destroy(); 308 } 309 310 311 public synchronized ToEclipseQueue getEclipseStdin() throws EclipseTerminatedException 312 { 313 eclipseConnection.testTerminated(); 314 if(useQueues) 315 return(stdin); 316 else 317 return(null); 318 } 319 320 public synchronized FromEclipseQueue getEclipseStdout() throws EclipseTerminatedException 321 { 322 eclipseConnection.testTerminated(); 323 if(useQueues) 324 return(stdout); 325 else 326 return(null); 327 } 328 329 public synchronized FromEclipseQueue getEclipseStderr() throws EclipseTerminatedException 330 { 331 eclipseConnection.testTerminated(); 332 if(useQueues) 333 return(stderr); 334 else 335 return(null); 336 } 337 338 /** 339 * Terminate the <i>OutOfProcessEclipse</i> process and the connection to it. 340 * After <code>destroy()</code> has been invoked, future invocations of public 341 * methods will throw <i>EclipseTerminatedException</i>s 342 * 343 * @throws EclipseTerminatedException if the <code>destroy()</code> method had 344 * already been called. 345 */ 346 public void destroy() throws IOException 347 { 348 eclipseConnection.testTerminated(); 349 try // multilateral disconnect 350 { 351 eclipseConnection.disconnect(); 352 } 353 catch(Exception e) 354 { // if that doesnt work, try unilateral 355 try 356 { 357 eclipseConnection.unilateralDisconnect(); 358 } 359 catch(EclipseTerminatedException ete) 360 {} 361 } 362 } 363 364 public CompoundTerm rpc(String goal) throws EclipseException, IOException 365 { 366 return(eclipseConnection.rpc(goal)); 367 } 368 369 public CompoundTerm rpc(CompoundTerm goal) throws EclipseException, IOException 370 { 371 return(eclipseConnection.rpc(goal)); 372 } 373 374 public FromEclipseQueue getFromEclipseQueue(String name) throws EclipseException, IOException 375 { 376 return(eclipseConnection.getFromEclipseQueue(name)); 377 } 378 379 public ToEclipseQueue getToEclipseQueue(String name) throws EclipseException, IOException 380 { 381 return(eclipseConnection.getToEclipseQueue(name)); 382 } 383 384 public AsyncEclipseQueue getAsyncEclipseQueue(String name) throws EclipseException, IOException 385 { 386 return(eclipseConnection.getAsyncEclipseQueue(name)); 387 } 388 389 public void compile(File f) throws EclipseException, IOException 390 { 391 eclipseConnection.compile(f); 392 } 393 394 public String getPath(File f) throws EclipseException, IOException 395 { 396 return(eclipseConnection.getPath(f)); 397 } 398 399 public CompoundTerm rpc(String functor, Object arg1) throws EclipseException, IOException 400 { 401 return(eclipseConnection.rpc(functor, arg1)); 402 } 403 404 public CompoundTerm rpc(String functor, Object arg1, 405 Object arg2) throws EclipseException, IOException 406 { 407 return(eclipseConnection.rpc(functor, arg1, arg2)); 408 } 409 410 public CompoundTerm rpc(String functor, Object arg1, 411 Object arg2, Object arg3) throws EclipseException, IOException 412 { 413 return(eclipseConnection.rpc(functor, arg1, arg2, arg3)); 414 } 415 416 public CompoundTerm rpc(String functor, Object arg1, 417 Object arg2, Object arg3, Object arg4) 418 throws EclipseException, IOException 419 { 420 return(eclipseConnection.rpc(functor, arg1, arg2, arg3, arg4)); 421 } 422 423 public CompoundTerm rpc(String functor, Object arg1, 424 Object arg2, Object arg3, Object arg4, 425 Object arg5) throws EclipseException, IOException 426 { 427 return(eclipseConnection.rpc(functor, arg1, arg2, arg3, arg4, arg5)); 428 } 429 430 public CompoundTerm rpc(String functor, Object[] args) throws EclipseException, IOException 431 { 432 return(eclipseConnection.rpc(functor, args)); 433 } 434 435 public CompoundTerm rpc(Object[] goalTerm) throws EclipseException, IOException 436 { 437 return(eclipseConnection.rpc(goalTerm)); 438 } 439 440 public Atom getPeerName() 441 { 442 return(eclipseConnection.getPeerName()); 443 } 444 445 private class StdInListener implements QueueListener 446 { 447 // size of byte chunk we attempt to read from stdin when there is a request 448 private static final int READ_CHUNK_SIZE = 512; 449 450 public void dataAvailable(Object source) 451 { 452 } 453 454 public void dataRequest(Object source) 455 { 456 ToEclipseQueue teq = (ToEclipseQueue) source; 457 458 byte[] chunk = new byte[READ_CHUNK_SIZE]; 459 int readResult ; 460 461 try 462 { 463 readResult = System.in.read(chunk); 464 if(readResult == -1) // EOF 465 { 466 // do nothing? 467 } 468 else // write the part of chunk to queue 469 { 470 teq.write(chunk, 0, readResult); 471 teq.flush(); 472 } 473 } 474 catch(IOException ioe) 475 { 476 // write these out to stderr 477 System.err.println("Attempt to read from JVM's stdin produced exception:\n"+ioe); 478 System.err.flush(); 479 } 480 } 481 } 482 483 private class StdOutListener implements QueueListener 484 { 485 private String destination; 486 487 public StdOutListener(String destination) 488 { 489 this.destination = destination; 490 } 491 public void dataAvailable(Object source) 492 { 493 FromEclipseQueue feq = (FromEclipseQueue) source; 494 byte[] chunk; 495 496 try 497 { 498 int availableBytes = feq.available(); 499 chunk = new byte[availableBytes]; 500 feq.read(chunk); 501 } 502 catch(IOException ioe) 503 { 504 // write these out to stderr 505 System.err.println("Attempt to use ECLiPSe's output queue produced exception:\n" 506 +ioe); 507 System.err.flush(); 508 return; 509 } 510 if(destination.equals("output")) 511 { 512 System.out.print(new String(chunk)); 513 return; 514 } 515 if(destination.equals("error")) 516 { 517 System.err.print(new String(chunk)); 518 return; 519 } 520 521 } 522 523 public void dataRequest(Object source) 524 { 525 } 526 } 527 528 // implements method from EclipseConnection 529 public EclipseMultitaskConnection registerMultitask(MultitaskListener multitaskListener) throws EclipseException,IOException { 530 return eclipseConnection.registerMultitask(multitaskListener); 531 } 532} 533