Java tutorial
/* * Copyright 2007, 2008 Ange Optimization ApS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @author Kim Hansen */ package dk.ange.octave.exec; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.Random; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import dk.ange.octave.exception.OctaveException; import dk.ange.octave.exception.OctaveIOException; import dk.ange.octave.util.NamedThreadFactory; import dk.ange.octave.util.NoCloseWriter; import dk.ange.octave.util.ReaderWriterPipeThread; import dk.ange.octave.util.TeeWriter; /** * The object connecting to the octave process */ public final class OctaveExec { /** * System property where the executable is found */ public static final String PROPERTY_EXECUTABLE = "dk.ange.octave.executable"; private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory .getLog(OctaveExec.class); private static final String[] CMD_ARRAY = { null, "--no-history", "--no-init-file", "--no-line-editing", "--no-site-file", "--silent" }; private final Process process; private final Writer processWriter; private final BufferedReader processReader; private final ExecutorService executor = Executors.newFixedThreadPool(2, new NamedThreadFactory(OctaveExec.class.getSimpleName())); private final ReaderWriterPipeThread errorStreamThread; private boolean destroyed = false; /** * Will start the octave process. * * @param stdinLog * This writer will capture all that is written to the octave process via stdin, if null the data will * not be captured. * @param stderrLog * This writer will capture all that is written from the octave process on stderr, if null the data will * not be captured. * @param octaveProgram * This is the path to the octave program, if it is null the program will be found using the system * property 'dk.ange.octave.executable' and if that is not set 'octave' will be assumed to be in the * PATH. * @param environment * The environment for the octave process, if null the process will inherit the environment for the * virtual machine. * @param workingDir * This will be the working dir for the octave process, if null the process will inherit the working dir * of the current process. */ public OctaveExec(final Writer stdinLog, final Writer stderrLog, final File octaveProgram, final String[] environment, final File workingDir) { final String[] cmdArray = CMD_ARRAY.clone(); if (octaveProgram != null) { cmdArray[0] = octaveProgram.getPath(); } else { cmdArray[0] = System.getProperty(PROPERTY_EXECUTABLE, "octave"); } try { process = Runtime.getRuntime().exec(cmdArray, environment, workingDir); } catch (final IOException e) { throw new OctaveIOException(e); } // Connect stderr errorStreamThread = ReaderWriterPipeThread.instantiate(new InputStreamReader(process.getErrorStream()), stderrLog); // Connect stdout processReader = new BufferedReader( new InputStreamReader(process.getInputStream(), Charset.forName("Latin1"))); // Connect stdin if (stdinLog == null) { processWriter = new OutputStreamWriter(process.getOutputStream()); } else { processWriter = new TeeWriter(new NoCloseWriter(stdinLog), new OutputStreamWriter(process.getOutputStream())); } } private final Random random = new Random(); private String generateSpacer() { return "-=+X+=- Octave.java spacer -=+X+=- " + random.nextLong() + " -=+X+=-"; } /** * @param input * @param output */ public void eval(final WriteFunctor input, final ReadFunctor output) { final String spacer = generateSpacer(); final Future<Void> writerFuture = executor.submit(new OctaveWriterCallable(processWriter, input, spacer)); final Future<Void> readerFuture = executor.submit(new OctaveReaderCallable(processReader, output, spacer)); final RuntimeException writerException = getFromFuture(writerFuture); if (writerException instanceof CancellationException) { log.error("Did not expect writer to be canceled", writerException); } if (writerException != null) { if (writerException instanceof CancellationException) { log.error("Did not expect writer to be canceled", writerException); } readerFuture.cancel(true); } final RuntimeException readerException = getFromFuture(readerFuture); if (writerException != null) { throw writerException; } if (readerException != null) { // Only gets here when writerException==null, and in that case we don't expect the reader to be cancelled if (readerException instanceof CancellationException) { log.error("Did not expect reader to be canceled", writerException); } throw readerException; } } private RuntimeException getFromFuture(final Future<Void> future) { try { future.get(); } catch (final InterruptedException e) { final String message = "InterruptedException should not happen"; log.error(message, e); return new RuntimeException(message, e); } catch (final ExecutionException e) { if (e.getCause() instanceof OctaveException) { final OctaveException oe = (OctaveException) e.getCause(); return reInstantiateException(oe); } // Can happen when there is an error in a OctaveWriter final String message = "ExecutionException should not happen"; log.error(message, e); return new RuntimeException(message, e); } catch (final CancellationException e) { return e; } catch (final RuntimeException e) { final String message = "RuntimeException should not happen"; log.error(message, e); return new RuntimeException(message, e); } return null; } private OctaveException reInstantiateException(final OctaveException inException) { final OctaveException outException; try { outException = inException.getClass().getConstructor(String.class, Throwable.class) .newInstance(inException.getMessage(), inException); } catch (final Exception e) { throw new IllegalStateException("Exception should not happen", e); } if (isDestroyed()) { outException.setDestroyed(true); } return outException; } private synchronized void setDestroyed(final boolean destroyed) { this.destroyed = destroyed; } private synchronized boolean isDestroyed() { return destroyed; } /** * Kill the octave process without remorse */ public void destroy() { setDestroyed(true); executor.shutdownNow(); process.destroy(); errorStreamThread.close(); try { processWriter.close(); } catch (final IOException e) { log.debug("Ignored error from processWriter.close() in OctaveExec.destroy()", e); } } /** * Close the octave process in an orderly fashion. */ public void close() { try { // it is not worth it to rewrite this to use eval() and some specialised Functors processWriter.write("exit\n"); processWriter.close(); final String read1 = processReader.readLine(); // Allow a single blank line, exit in octave 3.2 returns that: if (read1 != null && !"".equals(read1)) { throw new OctaveIOException("Expected a blank line, read '" + read1 + "'"); } final String read2 = processReader.readLine(); if (read2 != null) { throw new OctaveIOException("Expected reader to be at end of stream, read '" + read2 + "'"); } processReader.close(); errorStreamThread.close(); final int exitValue; try { exitValue = process.waitFor(); } catch (final InterruptedException e) { throw new OctaveIOException("Interrupted when waiting for octave process to terminate", e); } if (exitValue != 0) { throw new OctaveIOException("octave process terminated abnormaly, exitValue=" + exitValue); } } catch (final IOException e) { final OctaveIOException octaveException = new OctaveIOException("reader error", e); if (isDestroyed()) { octaveException.setDestroyed(true); } throw octaveException; } finally { executor.shutdown(); } } /** * @param writer * the new writer to write the error output to */ public void setErrorWriter(final Writer writer) { errorStreamThread.setWriter(writer); } }