package org.simantics.gnuplot; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; /** * @author Tuukka Lehtonen * @since 1.24 */ public class Gnuplot { private static final String GNUPLOT_VERSION_STRING_STARTS_WITH = "gnuplot "; private static final String GNUPLOT_ENV = "GNUPLOT"; private static final String PATH_ENV = "PATH"; private Path executable; public Gnuplot(Path executable) throws IOException { if (!Files.exists(executable)) throw new IOException("Provided gnuplot executable does not exist: " + executable); if (!Files.isExecutable(executable)) throw new IOException("Provided gnuplot executable is not marked executable: " + executable); this.executable = executable; } @Override public String toString() { return executable.toString(); } public static Gnuplot detect() throws IOException { try { return new Gnuplot(findGnuplotPath()); } catch (InterruptedException e) { throw new IOException(e); } } private static String readInput(InputStream in) throws IOException { StringWriter sw = new StringWriter(); for (int l = 0; l < GNUPLOT_VERSION_STRING_STARTS_WITH.length(); ++l) { int c; c = in.read(); if(c <= 0) break; sw.write(c); } return sw.toString(); } private static boolean testExecutable(Path exe) throws IOException, InterruptedException { Process process = new ProcessBuilder(exe.toString(), "-V").start(); try { if (GNUPLOT_VERSION_STRING_STARTS_WITH.startsWith( readInput(process.getInputStream()) )) return true; return false; } finally { process.getInputStream().close(); process.getErrorStream().close(); process.waitFor(); } } private static Path findExecutable(String[] paths, String executableName, boolean fail) throws IOException, InterruptedException { for (String p : paths) { Path exe = Paths.get(p); if (executableName != null) exe = exe.resolve(executableName); if (Files.exists(exe) && Files.isExecutable(exe) && testExecutable(exe)) { return exe; } } if (fail) throw new UnsupportedOperationException("Couldn't find executable '" + executableName + "' on the system path."); return null; } private static Path findExecutable(String path, boolean splitPath, boolean fail) throws IOException, InterruptedException { switch (OSType.calculate()) { case APPLE: case SUN: case LINUX: return findExecutable(splitPath ? path.split(":") : new String[] { path }, "gnuplot", fail); case WINDOWS: return findExecutable(splitPath ? path.split(";") : new String[] { path }, "gnuplot.exe", fail); default: throw new UnsupportedOperationException("unsupported platform"); } } private static Path findGnuplotPath() throws IOException, InterruptedException { String path = System.getenv(GNUPLOT_ENV); if (path != null) { // Does GNUPLOT point directly to the executable? Path p = findExecutable(new String[] { path }, null, false); if (p != null) return p; // Does GNUPLOT point to the gnuplot installation bin directory? p = findExecutable(path, false, false); if (p != null) return p; // Does GNUPLOT point to the gnuplot installation root directory? p = findExecutable(path + "/bin", false, false); if (p != null) return p; } path = System.getenv(PATH_ENV); return findExecutable(path, true, true); } private void execute0(Path workingDirectory, Path stdout, Path stderr, String... cmdline) throws IOException { ProcessBuilder builder = new ProcessBuilder(cmdline) .directory(workingDirectory.toFile()); if (stdout != null) builder.redirectOutput(stdout.toFile()); if (stderr != null) builder.redirectError(stderr.toFile()); Process process = builder.start(); Thread stdoutDumper = stdout == null ? new Thread(new InputStreamToFileCopier(process.getInputStream(), null)) : null; Thread stderrDumper = stderr == null ? new Thread(new InputStreamToFileCopier(process.getErrorStream(), null)) : null; if (stdoutDumper != null) stdoutDumper.start(); if (stderrDumper != null) stderrDumper.start(); process.getOutputStream().close(); try { if (stdoutDumper != null) stdoutDumper.join(); if (stderrDumper != null) stderrDumper.join(); process.waitFor(); } catch (InterruptedException e) { throw new IOException(e); } } public void execute(Path workingDirectory, Path stdout, Path stderr, boolean redirectStderr, InputStream input, String charset) throws IOException { try (GnuplotSession session = newSession(workingDirectory, stdout, stderr, redirectStderr)) { session.evaluateStream(input, charset); } } public void execute(Path workingDirectory, Path stdout, Path stderr, Path script) throws IOException { execute0(workingDirectory, stdout, stderr, executable.toString(), "-c", script.toString()); } public void execute(Path workingDirectory, Path stdout, Path stderr, String... commands) throws IOException { StringBuilder e = new StringBuilder().append('"'); boolean first = true; for (String cmd : commands) { if (!first) e.append("; "); first = false; e.append(cmd); } e.append('"'); execute0(workingDirectory, stdout, stderr, executable.toString(), "-e", e.toString()); } private ProcessBuilder buildSessionProcess(Path workingDirectory, Path stdout, Path stderr, boolean redirectErrorStream) { ProcessBuilder builder = new ProcessBuilder(executable.toString()) .directory(workingDirectory != null ? workingDirectory.toFile() : null) .redirectErrorStream(redirectErrorStream); if (stdout != null) builder.redirectOutput(stdout.toFile()); if (stderr != null) builder.redirectError(stderr.toFile()); return builder; } public GnuplotSession newSession(Path workingDirectory, Path stdout) throws IOException { return newSession(workingDirectory, stdout, null, true); } public GnuplotSession newSession(Path workingDirectory, Path stdout, Path stderr, boolean redirectStderr) throws IOException { return new GnuplotSession(workingDirectory, stdout == null, stderr == null, buildSessionProcess(workingDirectory, stdout, stderr, redirectStderr).start()); } static enum OSType { APPLE, LINUX, SUN, WINDOWS, UNKNOWN; public static OSType calculate() { String osName = System.getProperty("os.name"); assert osName != null; osName = osName.toLowerCase(); if (osName.startsWith("mac os x")) return APPLE; if (osName.startsWith("windows")) return WINDOWS; if (osName.startsWith("linux")) return LINUX; if (osName.startsWith("sun")) return SUN; return UNKNOWN; } } }