org.jsweet.transpiler.util.ProcessUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.jsweet.transpiler.util.ProcessUtil.java

Source

/* 
 * JSweet transpiler - http://www.jsweet.org
 * Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jsweet.transpiler.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

/**
 * A set of utilities to launch external processes from Java.
 * 
 * @author Renaud Pawlak
 */
public class ProcessUtil {
    private final static Logger logger = Logger.getLogger(ProcessUtil.class);

    private static boolean initialized = false;

    /**
     * Initializes the node command paths (OS-specific initializations).
     */
    public static void initNode() {
        if (!initialized) {
            // hack for OSX Eclipse's path issue
            if (!System.getenv("PATH").contains("/usr/local/bin") && new File("/usr/local/bin/node").exists()) {
                ProcessUtil.EXTRA_PATH = "/usr/local/bin";
                ProcessUtil.NODE_COMMAND = "/usr/local/bin/node";
                ProcessUtil.NPM_COMMAND = "/usr/local/bin/npm";
            }
            initialized = true;
        }
    }

    /**
     * A static field that stores the user home directory.
     */
    public static File USER_HOME_DIR = new File(System.getProperty("user.home"));

    /**
     * A static field that stores the JSweet npm directory.
     */
    public static File NPM_DIR = new File(USER_HOME_DIR, ".jsweet-node_modules");

    private static List<String> nodeCommands = Arrays.asList("tsc", "browserify");

    /**
     * The node command name (can be full path in some environments).
     */
    public static String NODE_COMMAND = "node";

    /**
     * The npm command name (can be full path in some environments).
     */
    public static String NPM_COMMAND = "npm";

    /**
     * Some extra paths to be added to the PATH environment variable in some
     * environments. Typically Eclipse on Mac OSX misses the /usr/local/bin
     * path, which is required to run node.
     */
    public static String EXTRA_PATH;

    /**
     * Gets the full path of a command installed with npm.
     */
    private static String getNpmPath(String command) {
        if (System.getProperty("os.name").startsWith("Windows")) {
            return NPM_DIR.getPath() + File.separator + command + ".cmd";
        } else {
            return NPM_DIR.getPath() + File.separator + "bin" + File.separator + command;
        }
    }

    /**
     * Tells if this node command is installed.
     */
    public static boolean isInstalledWithNpm(String command) {
        return new File(getNpmPath(command)).exists();
    }

    /**
     * Runs the given command.
     * 
     * @param command
     *            the command name
     * @param stdoutConsumer
     *            consumes the standard output stream as lines of characters
     * @param errorHandler
     *            upcalled when the command does not terminate successfully
     * @param args
     *            the command-line arguments
     * @return the process that was created to execute the command (exited at
     *         this point)
     */
    public static Process runCommand(String command, Consumer<String> stdoutConsumer, Runnable errorHandler,
            String... args) {
        return runCommand(command, null, false, stdoutConsumer, null, errorHandler, args);
    }

    /**
     * Runs the given command in an asynchronous manner.
     * 
     * @param command
     *            the command name
     * @param stdoutConsumer
     *            consumes the standard output stream as lines of characters
     * @param endConsumer
     *            called when the process actually ends
     * @param errorHandler
     *            upcalled when the command does not terminate successfully
     * @param args
     *            the command-line arguments
     * @return the process that was created to execute the command (can be still
     *         running at this point)
     */
    public static Process runAsyncCommand(String command, Consumer<String> stdoutConsumer,
            Consumer<Process> endConsumer, Runnable errorHandler, String... args) {
        return runCommand(command, null, true, stdoutConsumer, endConsumer, errorHandler, args);
    }

    /**
     * Runs the given command.
     * 
     * @param command
     *            the command name
     * @param directory
     *            the working directory of the created process
     * @param async
     *            tells if the command should be run asynchronously (in a
     *            separate thread)
     * @param stdoutConsumer
     *            consumes the standard output stream as lines of characters
     * @param endConsumer
     *            called when the process actually ends
     * @param errorHandler
     *            upcalled when the command does not terminate successfully
     * @param args
     *            the command-line arguments
     * @return the process that was created to execute the command (can be still
     *         running at this point if <code>async</code> is <code>true</code>)
     */
    public static Process runCommand(String command, File directory, boolean async, Consumer<String> stdoutConsumer,
            Consumer<Process> endConsumer, Runnable errorHandler, String... args) {

        String[] cmd;
        if (System.getProperty("os.name").startsWith("Windows")) {
            if (nodeCommands.contains(command)) {
                cmd = new String[] { getNpmPath(command) };
            } else {
                cmd = new String[] { "cmd", "/c", command };
            }
        } else {
            if (nodeCommands.contains(command)) {
                cmd = new String[] { getNpmPath(command) };
            } else {
                cmd = new String[] { command };
            }
        }
        cmd = ArrayUtils.addAll(cmd, args);

        logger.debug("run command: " + StringUtils.join(cmd, " "));
        Process[] process = { null };
        try {
            ProcessBuilder processBuilder = new ProcessBuilder(cmd);
            processBuilder.redirectErrorStream(true);
            if (directory != null) {
                processBuilder.directory(directory);
            }
            if (!StringUtils.isBlank(EXTRA_PATH)) {
                processBuilder.environment().put("PATH",
                        processBuilder.environment().get("PATH") + File.pathSeparator + EXTRA_PATH);
            }

            process[0] = processBuilder.start();

            Runnable runnable = new Runnable() {

                @Override
                public void run() {
                    try {
                        try (BufferedReader in = new BufferedReader(
                                new InputStreamReader(process[0].getInputStream()))) {
                            String line;
                            while ((line = in.readLine()) != null) {
                                if (stdoutConsumer != null) {
                                    stdoutConsumer.accept(line);
                                } else {
                                    logger.info(command + " - " + line);
                                }
                            }
                        }

                        process[0].waitFor();
                        if (endConsumer != null) {
                            endConsumer.accept(process[0]);
                        }
                        if (process[0].exitValue() != 0) {
                            if (errorHandler != null) {
                                errorHandler.run();
                            }
                        }
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                        if (errorHandler != null) {
                            errorHandler.run();
                        }
                    }
                }
            };
            if (async) {
                new Thread(runnable).start();
            } else {
                runnable.run();
            }

        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            if (errorHandler != null) {
                errorHandler.run();
            }
            return null;
        }
        return process[0];
    }

    /**
     * Installs a <code>node<code> package with <code>npm</code> (assumes that
     * <code>node</code> is installed).
     * 
     * @param nodePackageName
     *            the package name
     * @param global
     *            <code>true</code> for adding the <code>-g</code> option
     */
    public static void installNodePackage(String nodePackageName, String version, boolean global) {
        logger.debug("installing " + nodePackageName + " with npm");
        initNode();
        if (global) {
            runCommand(NPM_COMMAND, USER_HOME_DIR, false, null, null, null, "install", "--prefix",
                    NPM_DIR.getPath(), version == null ? nodePackageName : nodePackageName + "@" + version, "-g");
        } else {
            runCommand(NPM_COMMAND, USER_HOME_DIR, false, null, null, null, "install",
                    version == null ? nodePackageName : nodePackageName + "@" + version, "--save");
        }
    }

    /**
     * Checks if a node package has been installed locally.
     * 
     * @param nodePackageName
     *            the node module to be tested
     * @return true if already installed locally
     */
    public static boolean isNodePackageInstalled(String nodePackageName) {
        logger.debug("checking installation of " + nodePackageName + " with npm");
        initNode();
        boolean[] installed = { false };
        runCommand(NPM_COMMAND, USER_HOME_DIR, false, line -> {
            if (!installed[0]) {
                installed[0] = line.endsWith("/" + nodePackageName);
            }
        }, null, null, "ls", "--parseable", nodePackageName);
        return installed[0];
    }

    /**
     * Uninstalls a <code>node<code> package with <code>npm</code> (assumes that
     * <code>node</code> is installed).
     * 
     * @param nodePackageName
     *            the package name
     * @param global
     *            <code>true</code> for adding the <code>-g</code> option
     */
    public static void uninstallNodePackage(String nodePackageName, boolean global) {
        logger.debug("uninstalling " + nodePackageName + " with npm");
        initNode();
        if (global) {
            runCommand(NPM_COMMAND, USER_HOME_DIR, false, null, null, null, "uninstall", "--prefix",
                    NPM_DIR.getPath(), nodePackageName, "-g");
        } else {
            runCommand(NPM_COMMAND, USER_HOME_DIR, false, null, null, null, "uninstall", nodePackageName);
        }
    }

}