All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.kevinarpe.papaya.process.ProcessBuilder2 Maven / Gradle / Ivy

package com.googlecode.kevinarpe.papaya.process;

/*
 * #%L
 * This file is part of Papaya.
 * %%
 * Copyright (C) 2013 - 2014 Kevin Connor ARPE ([email protected])
 * %%
 * Papaya 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.
 * 
 * GPL Classpath Exception:
 * This project is subject to the "Classpath" exception as provided in
 * the LICENSE file that accompanied this code.
 * 
 * Papaya 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 Papaya.  If not, see .
 * #L%
 */

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.google.common.base.Joiner;
import com.googlecode.kevinarpe.papaya.annotation.FullyTested;
import com.googlecode.kevinarpe.papaya.argument.ArrayArgs;
import com.googlecode.kevinarpe.papaya.argument.CollectionArgs;
import com.googlecode.kevinarpe.papaya.argument.ObjectArgs;
import com.googlecode.kevinarpe.papaya.argument.PathArgs;

/**
 * This class is used to spawn external child processes by calling {@link #start()}.  Instances of
 * {@link Process2} are used to manage these spawned child processes.  Multiple child processes may
 * be spawned from a single instance of {@link ProcessBuilder2}.
 * 

* This class, in conjunction with class {@code Process2}, act as powerful replacements for * {@link ProcessBuilder} and {@link Process}, respectively. These default implementations * included in the JDK are deceptively simple and unfortunately easily misused. *

* Due to the nature of modern operating system processes, there are normally three streams * associated with each process: STDIN, STDOUT, and STDERR. Instances of {@link Process2} will * construct helper threads as necessary to ensure none of these streams is blocking the child * process. *

* For example, if the input (via STDIN) to a process is huge (many megabytes or more), normally, * the receiving process cannot consume the input in a single non-blocking call. Instead, the * child process will block to process the incoming data, then resume reading from STDIN. The same * is true for the output streams: STDOUT and STDERR. If large amounts of data are produced by * STDOUT or STDERR, the child process will block until this data is consumed. *

* While normal uses of STDIN, STDOUT, and STDERR are human-readable text, provisions are made in * this class to provide byte-oriented streams as well as character-oriented streams. These may be * mixed and matched as necessary. *

* Where possible, setter methods return a reference to {@code this}, facilitating interface * fluency. *

* Finally, as processes will vary in their time to termination, a helpful set of methods has been * added to {@link Process2} to wait for the exit value in a friendly and convenient manner. *

* Here are some extensions on top of {@code ProcessBuilder}: *

    *
  • Set text for STDIN via {@link ProcessBuilder2#stdinText(String)}
  • *
  • Set raw bytes for STDIN via {@link ProcessBuilder2#stdinData(byte[])}
  • *
  • Configure STDOUT handling via {@link ProcessBuilder2#stdoutSettings()}
  • *
  • Configure STDERR handling via {@link ProcessBuilder2#stderrSettings()}
  • *
* * @author Kevin Connor ARPE ([email protected]) */ @FullyTested public class ProcessBuilder2 extends AbstractProcessSettings { private final ProcessBuilder _processBuilder; private byte[] _optStdinByteArr; private String _optStdinText; /** * Forwards to {@link ProcessBuilder#ProcessBuilder(List)}. */ public ProcessBuilder2(List command) { CollectionArgs.checkElementsNotNull(command, "command"); _processBuilder = new ProcessBuilder(command); } /** * Forwards to {@link ProcessBuilder#ProcessBuilder(String...)}. */ public ProcessBuilder2(String... commandArr) { this(new ArrayList(Arrays.asList(commandArr))); } /** * Copy constructor * * @param pb * reference to another instance of {@link ProcessBuilder2} * * @throws NullPointerException * if {@code pb} is {@code null} */ public ProcessBuilder2(ProcessBuilder2 pb) { super(ObjectArgs.checkNotNull(pb, "pb"), ChildProcessState.HAS_NOT_STARTED); _processBuilder = new ProcessBuilder(pb.command()); Map env = this.environment(); env.clear(); Map env2 = pb.environment(); env.putAll(env2); File dirPath = pb.directory(); this.directory(dirPath); boolean isRedirected = pb.redirectErrorStream(); this.redirectErrorStream(isRedirected); byte[] stdinData = pb.stdinData(); String stdinText = pb.stdinText(); if (null != stdinData) { this.stdinData(stdinData); } if (null != stdinText) { this.stdinText(stdinText); } } /** * Forwards to {@link ProcessBuilder#command(List)}. *

* Each argument, include the program name, should be a separate element, which need not be * quoted with {@code ""} or {@code ''}. Quotes allow shells to parse command lines. * * @return reference to {@code this} * * @throws NullPointerException * if {@code command} is {@code null} * * @see #command(String...) * @see #command() */ public ProcessBuilder2 command(List command) { CollectionArgs.checkElementsNotNull(command, "command"); _processBuilder.command(command); return this; } /** * Forwards to {@link ProcessBuilder#command(String...)}. *

* Each argument, include the program name, should be a separate element, which need not be * quoted with {@code ""} or {@code ''}. Quotes allow shells to parse command lines. * * @return reference to {@code this} * * @see #command(List) * @see #command() */ public ProcessBuilder2 command(String... command) { ArrayArgs.checkElementsNotNull(command, "command"); _processBuilder.command(command); return this; } /** * Forwards to {@link ProcessBuilder#command()}. * * @see #command(List) * @see #command(String...) */ @Override public List command() { return _processBuilder.command(); } /** * Forwards to {@link ProcessBuilder#environment()}. */ @Override public Map environment() { return _processBuilder.environment(); } /** * Forwards to {@link ProcessBuilder#directory()}. * * @return (optional) override working directory for child process. May be {@code null}. * There is no guarantee the path is a directory. * * @see #directory(File) */ @Override public File directory() { return _processBuilder.directory(); } /** * Forwards to {@link ProcessBuilder#directory(File)}. * * @param directory * (optional) override working directory for child process. May be {@code null}. * The path is only checked if directory when {@link #start()} is called. * * @return reference to {@code this} * * @see #directory() */ public ProcessBuilder2 directory(File directory) { _processBuilder.directory(directory); return this; } /** * Forwards to {@link ProcessBuilder#redirectErrorStream()}. * * @see #redirectErrorStream(boolean) */ @Override public boolean redirectErrorStream() { return _processBuilder.redirectErrorStream(); } /** * Forwards to {@link ProcessBuilder#redirectErrorStream(boolean)}. *

* Generally, it is not recommended to use this feature, as this class readily * facilitates the capture and processing of STDOUT and STDERR separately. In the common case * where all output is logged, it is helpful for debugging to mark each line of text (or block * of byte data) with its source stream: STDOUT or STDERR. * * @return reference to {@code this} * * @see #redirectErrorStream() */ public ProcessBuilder2 redirectErrorStream(boolean redirectErrorStream) { _processBuilder.redirectErrorStream(redirectErrorStream); return this; } /** * Sets array of bytes to write to STDIN stream in new process. *

* This method clears any text previously set with {@link #stdinText(String)}. * * @param optByteArr *

    *
  • (optional) array of bytes for STDIN stream
  • *
  • May be {@code null} or empty, but empty is treated as {@code null}.
  • *
  • This array is not copied.
  • *
* * @return reference to {@code this} * * @see #stdinText(String) * @see #stdinData() */ public ProcessBuilder2 stdinData(byte[] optByteArr) { _optStdinText = null; if (null == optByteArr || 0 == optByteArr.length) { _optStdinByteArr = null; } else { _optStdinByteArr = optByteArr; } return this; } /** * Sets text to write to STDIN stream in new process. This text is not written directly to * STDIN stream. Instead, the bytes first are extracted via {@link String#getBytes()}, then * written to STDIN stream. *

* This method clears any bytes previously set with {@link #stdinData(byte[])}. * * @param optStr *

    *
  • (optional) text for STDIN stream
  • *
  • May be {@code null} or empty, but empty is treated as {@code null}.
  • *
* * @return reference to {@code this} * * @see String#getBytes() * @see #stdinData(byte[]) * @see #stdinText() */ public ProcessBuilder2 stdinText(String optStr) { _optStdinByteArr = null; if (null == optStr || optStr.isEmpty()) { _optStdinText = null; } else { _optStdinText = optStr; } return this; } /** * Retrieves the optional array of bytes to be written to the STDIN stream of the new process. * The return value is not a copy. Callers may directly modify the array elements. *

* If the return value is not {@code null}, the return value of {@link #stdinText()} is * guaranteed to be {@code null}. * * @return array of bytes for STDIN. Never an empty array. May be {@code null}. * * @see #stdinData(byte[]) * @see #stdinText(String) * @see #stdinText() */ @Override public byte[] stdinData() { return _optStdinByteArr; } @Override protected byte[] stdinDataRef() { return _optStdinByteArr; } /** * Retrieves the optional text to be written to the STDIN stream of the new process. *

* If the return value is not {@code null}, the return value of {@link #stdinData()} is * guaranteed to be {@code null}. * * @return text for STDIN. Never an empty {@link String}. May be {@code null}. * * @see #stdinText(String) * @see #stdinData(byte[]) * @see #stdinData() */ @Override public String stdinText() { return _optStdinText; } /** * Spawns a child process and creates an instance of {@link Process2} to manage and control * this new child process. * * @return handle to new child process * * @throws IllegalArgumentException * if {@link #command()} is empty or contains {@code null} values * @throws IOException *

    *
  • if {@link #directory()} is not {@code null} and is not a directory
  • *
  • if new child process cannot be started
  • *
*/ public Process2 start() throws IOException { List command = this.command(); try { CollectionArgs.checkNotEmptyAndElementsNotNull(command, "command"); } catch (Exception e) { throw new IllegalArgumentException("Arguments are invalid", e); } File directory = this.directory(); if (null != directory) { try { PathArgs.checkDirectoryExists(directory, "directory"); } catch (Exception e) { throw new IOException("Working directory override is invalid", e); } } Process p = null; try { p = _processBuilder.start(); } catch (IOException e) { List argList = command(); String s = String.format("Failed to start command: %s", argListToString(argList)); throw new IOException(s, e); } Process2 p2 = new Process2(this, p); return p2; } static String argListToString(List argList) { String x = "[" + Joiner.on(", ").join(argList) + "]"; return x; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy