/* * Eoulsan development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public License version 2.1 or * later and CeCILL-C. This should be distributed with the code. * If you do not have a copy, see: * * http://www.gnu.org/licenses/lgpl-2.1.txt * http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt * * Copyright for this code is held jointly by the Genomic platform * of the Institut de Biologie de l'École normale supérieure and * the individual authors. These should be listed in @author doc * comments. * * For more information on the Eoulsan project and its aims, * or to join the Eoulsan Google group, visit the home page * at: * * http://outils.genomique.biologie.ens.fr/eoulsan * */ package fr.ens.biologie.genomique.eoulsan.util; import static fr.ens.biologie.genomique.eoulsan.EoulsanLogger.getLogger; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import com.google.common.base.Joiner; import fr.ens.biologie.genomique.eoulsan.io.FileCharsets; /** * Utility class for launching external process. * @since 1.0 * @author Laurent Jourdren */ public final class ProcessUtils { /* Default Charset. */ private static final Charset CHARSET = Charset.forName(System.getProperty("file.encoding")); private static Random random; public static class ProcessResult { private final int exitValue; private final String stdout; private final String stderr; /** * Get the exit value of the process. * @return Returns the errorResult */ public int getExitValue() { return this.exitValue; } /** * Get the standard output of the process * @return Returns the stdout */ public String getStdout() { return this.stdout; } /** * Get the standard error of the process * @return Returns the stderr */ public String getStderr() { return this.stderr; } // // Constructor // /** * Constructor. * @param exitValue the exit value of the process * @param stdout the standard output of the process * @param stderr the standard error of the process */ private ProcessResult(final int exitValue, final String stdout, final String stderr) { this.exitValue = exitValue; this.stdout = stdout; this.stderr = stderr; } } /** * Execute a command. * @param cmd command to execute * @return the exit error of the program * @throws IOException */ public static int system(final String cmd) throws IOException { getLogger().fine( "execute (Thread " + Thread.currentThread().getId() + "): " + cmd); final Process p = Runtime.getRuntime().exec(cmd); try { return p.waitFor(); } catch (InterruptedException e) { throw new IOException(e); } } /** * Execute a command. * @param cmd command to execute * @return the exit error of the program * @throws IOException */ public static int sh(final List<String> cmd) throws IOException { return sh(cmd, null); } /** * Execute a command. * @param cmd command to execute * @param temporaryDirectory temporary where create the temporary shell file * @return the exit error of the program * @throws IOException if an error occurs while executing the command */ public static int sh(final List<String> cmd, final File temporaryDirectory) throws IOException { ProcessBuilder pb; Process p = null; int exitValue = Integer.MAX_VALUE; try { pb = new ProcessBuilder(cmd); if (!(temporaryDirectory == null)) { pb.directory(temporaryDirectory); } getLogger().fine("Execute command: " + cmd.toString()); p = pb.start(); final Thread terr = new Thread(new ProcessThreadErrOutput(p.getErrorStream())); terr.start(); terr.join(); exitValue = p.waitFor(); getLogger().fine("Command exit value: " + exitValue); } catch (InterruptedException e) { getLogger().warning("Process interrupted : " + e.getMessage()); } return exitValue; } /** * Execute a command with the OS. * @param cmd Command to execute * @param stdOutput don't show the result of the command on the standard * output * @throws IOException if an error occurs while running the process */ public static void exec(final String cmd, final boolean stdOutput) throws IOException { getLogger().fine( "execute (Thread " + Thread.currentThread().getId() + "): " + cmd); final long startTime = System.currentTimeMillis(); Process p = Runtime.getRuntime().exec(cmd); InputStream std = p.getInputStream(); BufferedReader stdr = new BufferedReader(new InputStreamReader(std, CHARSET)); String l = null; while ((l = stdr.readLine()) != null) { if (stdOutput) { System.out.println(l); } } InputStream err = p.getInputStream(); BufferedReader errr = new BufferedReader(new InputStreamReader(err, CHARSET)); String l2 = null; while ((l2 = errr.readLine()) != null) { System.err.println(l2); } stdr.close(); errr.close(); logEndTime(p, cmd, startTime); } /** * Execute a command with the OS and save the output in file. * @param cmd Command to execute * @param outputFile The output file * @throws IOException if an error occurs while running the process */ public static void execWriteOutput(final String cmd, final File outputFile) throws IOException { getLogger().fine( "execute (Thread " + Thread.currentThread().getId() + "): " + cmd); final long startTime = System.currentTimeMillis(); Process p = Runtime.getRuntime().exec(cmd); InputStream std = p.getInputStream(); final OutputStream fos = FileUtils.createOutputStream(outputFile); FileUtils.copy(std, fos); InputStream err = p.getInputStream(); BufferedReader errr = new BufferedReader(new InputStreamReader(err, CHARSET)); String l2 = null; while ((l2 = errr.readLine()) != null) { System.err.println(l2); } fos.close(); errr.close(); logEndTime(p, cmd, startTime); } /** * Execute a command with the OS and return the output in a string. * @param cmd Command to execute * @return a string with the output the command * @throws IOException if an error occurs while running the process */ public static String execToString(final String cmd) throws IOException { return execToString(cmd, false, true); } /** * Execute a command with the OS and return the output in a string. * @param cmd Command to execute * @param addStdErr add the output of stderr in the result * @return a string with the output the command * @throws IOException if an error occurs while running the process */ public static String execToString(final String cmd, final boolean addStdErr, final boolean checkExitCode) throws IOException { getLogger().fine( "execute (Thread " + Thread.currentThread().getId() + "): " + cmd); final long startTime = System.currentTimeMillis(); final Process p = Runtime.getRuntime().exec(new String[] {"/bin/sh", "-c", cmd}); final InputStream std = p.getInputStream(); final BufferedReader stdr = new BufferedReader(new InputStreamReader(std, CHARSET)); final StringBuilder sb = new StringBuilder(); String l1 = null; while ((l1 = stdr.readLine()) != null) { sb.append(l1); sb.append('\n'); } InputStream err = p.getErrorStream(); BufferedReader errr = new BufferedReader(new InputStreamReader(err, CHARSET)); String l2 = null; while ((l2 = errr.readLine()) != null) { if (addStdErr) { sb.append(l2); sb.append('\n'); } else { System.err.println(l2); } } stdr.close(); errr.close(); if (checkExitCode) { logEndTime(p, cmd, startTime); } return sb.toString(); } /** * Log the time of execution of a process. * @param p Process to log * @param cmd Command of the process * @param startTime Start time in ms * @throws IOException if an error occurs at the end of the process */ public static final void logEndTime(final Process p, final String cmd, final long startTime) throws IOException { try { final int exitValue = p.waitFor(); final long endTime = System.currentTimeMillis(); throwExitCodeException(exitValue, cmd); getLogger().fine("Done (Thread " + Thread.currentThread().getId() + ", exit code: " + exitValue + ") in " + (endTime - startTime) + " ms."); } catch (InterruptedException e) { getLogger().severe("Interrupted exception: " + e.getMessage()); } } /** * Throw an IOException if the exit code of a process is not equals to 0. * @param exitCode the exit code * @param command the executed command * @throws IOException if the exit code if not 0 */ public static final void throwExitCodeException(final int exitCode, final String command) throws IOException { switch (exitCode) { case 0: return; case 126: throw new IOException("Command invoked cannot execute: " + command); case 127: throw new IOException("Command not found: " + command); case 134: throw new IOException("Abort: " + command); case 139: throw new IOException("Segmentation fault: " + command); default: throw new IOException( "Error while executing (exit code " + exitCode + "): " + command); } } /** * Return a set withs pid of existing executable. * @return a set of integers with pid of existing executable */ public static Set<Integer> getExecutablePids(final String executableName) { if (executableName == null) { return null; } Set<Integer> result = new HashSet<>(); try { final String s = ProcessUtils.execToString("pgrep -x " + executableName.trim()); final String[] lines = s.split("\n"); for (String line : lines) { try { result.add(Integer.parseInt(line)); } catch (NumberFormatException e) { continue; } } } catch (IOException e) { return result; } return result; } /** * Wait the end of the execution of all the instance of an executable. * @param executableName name of the executable */ public static void waitUntilExecutableRunning(final String executableName) { if (executableName == null) { return; } while (true) { final Set<Integer> pids = getExecutablePids(executableName); if (pids.size() == 0) { return; } try { Thread.sleep(5000); } catch (InterruptedException e) { } } } /** * Wait a random number of milliseconds. * @param maxMilliseconds the maximum number of milliseconds to wait */ public static void waitRandom(final int maxMilliseconds) { if (maxMilliseconds <= 0) { return; } if (random == null) { random = new Random(System.currentTimeMillis()); } try { Thread.sleep(random.nextInt(maxMilliseconds)); } catch (InterruptedException e) { } } /** * This class allow to fetch standard error of the process, without printing */ public static final class ProcessThreadErrOutput implements Runnable { final InputStream err; public String exceptionMessage; BufferedReader buff; @Override public void run() { try { while (new BufferedReader( new InputStreamReader(this.err, FileCharsets.LATIN1_CHARSET)) .readLine() != null) { } this.err.close(); } catch (IOException e) { this.exceptionMessage = e.getMessage(); } } public ProcessThreadErrOutput(final InputStream err) { this.err = err; } } // ProcessThreadErrOutput /** * This class allow to write on a PrintStream the content of a BufferedReader * @author Laurent Jourdren */ private static final class ProcessThreadOutput implements Runnable { final BufferedReader reader; final PrintStream pw; @Override public void run() { String l = null; try { while ((l = this.reader.readLine()) != null) { this.pw.println(l); } } catch (IOException e) { e.printStackTrace(); } } ProcessThreadOutput(final BufferedReader reader, final PrintStream pw) { this.reader = reader; this.pw = pw; } } /** * Execute a command and write the content of the standard output and error to * System.out and System.err. * @param cmd Command to execute * @throws IOException if an error occurs while executing the command */ public static void execThreadOutput(final String cmd) throws IOException { getLogger().fine( "execute (Thread " + Thread.currentThread().getId() + "): " + cmd); final long startTime = System.currentTimeMillis(); Process p = Runtime.getRuntime().exec(cmd); final BufferedReader stdr = new BufferedReader(new InputStreamReader(p.getInputStream(), CHARSET)); final BufferedReader errr = new BufferedReader(new InputStreamReader(p.getErrorStream(), CHARSET)); new Thread(new ProcessThreadOutput(stdr, System.out)).start(); new Thread(new ProcessThreadOutput(errr, System.err)).start(); logEndTime(p, cmd, startTime); } /** * Execute a command and write the content of the standard output and error to * System.out and System.err. * @param cmd array with the command to execute * @throws IOException if an error occurs while executing the command */ public static void execThreadOutput(final String[] cmd) throws IOException { getLogger().fine("execute (Thread " + Thread.currentThread().getId() + "): " + Arrays.toString(cmd)); final long startTime = System.currentTimeMillis(); Process p = Runtime.getRuntime().exec(cmd); final BufferedReader stdr = new BufferedReader(new InputStreamReader(p.getInputStream(), CHARSET)); final BufferedReader errr = new BufferedReader(new InputStreamReader(p.getErrorStream(), CHARSET)); new Thread(new ProcessThreadOutput(stdr, System.out)).start(); new Thread(new ProcessThreadOutput(errr, System.err)).start(); logEndTime(p, Joiner.on(' ').join(cmd), startTime); } private ProcessUtils() { } }