/*
* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.incubator.jpackage.internal;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
final public class Executor {
Executor() {
}
Executor setOutputConsumer(Consumer<Stream<String>> v) {
outputConsumer = v;
return this;
}
Executor saveOutput(boolean v) {
saveOutput = v;
return this;
}
Executor setWriteOutputToFile(boolean v) {
writeOutputToFile = v;
return this;
}
Executor setProcessBuilder(ProcessBuilder v) {
pb = v;
return this;
}
Executor setCommandLine(String... cmdline) {
return setProcessBuilder(new ProcessBuilder(cmdline));
}
List<String> getOutput() {
return output;
}
Executor executeExpectSuccess() throws IOException {
int ret = execute();
if (0 != ret) {
throw new IOException(
String.format("Command %s exited with %d code",
createLogMessage(pb), ret));
}
return this;
}
int execute() throws IOException {
output = null;
boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput;
File outputFile = null;
if (needProcessOutput) {
pb.redirectErrorStream(true);
if (writeOutputToFile) {
outputFile = File.createTempFile("jpackageOutputTempFile", ".tmp");
pb.redirectOutput(outputFile);
}
} else {
// We are not going to read process output, so need to notify
// ProcessBuilder about this. Otherwise some processes might just
// hang up (`ldconfig -p`).
pb.redirectError(ProcessBuilder.Redirect.DISCARD);
pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
}
Log.verbose(String.format("Running %s", createLogMessage(pb)));
Process p = pb.start();
int code = 0;
if (writeOutputToFile) {
try {
code = p.waitFor();
} catch (InterruptedException ex) {
Log.verbose(ex);
throw new RuntimeException(ex);
}
}
if (needProcessOutput) {
final List<String> savedOutput;
Supplier<Stream<String>> outputStream;
if (writeOutputToFile) {
savedOutput = Files.readAllLines(outputFile.toPath());
outputFile.delete();
outputStream = () -> {
if (savedOutput != null) {
return savedOutput.stream();
}
return null;
};
if (Log.isVerbose()) {
outputStream.get().forEach(Log::verbose);
}
if (outputConsumer != null) {
outputConsumer.accept(outputStream.get());
}
} else {
try (var br = new BufferedReader(new InputStreamReader(
p.getInputStream()))) {
// Need to save output if explicitely requested (saveOutput=true) or
// if will be used used by multiple consumers
if ((outputConsumer != null && Log.isVerbose()) || saveOutput) {
savedOutput = br.lines().collect(Collectors.toList());
if (saveOutput) {
output = savedOutput;
}
} else {
savedOutput = null;
}
outputStream = () -> {
if (savedOutput != null) {
return savedOutput.stream();
}
return br.lines();
};
if (Log.isVerbose()) {
outputStream.get().forEach(Log::verbose);
}
if (outputConsumer != null) {
outputConsumer.accept(outputStream.get());
}
if (savedOutput == null) {
// For some processes on Linux if the output stream
// of the process is opened but not consumed, the process
// would exit with code 141.
// It turned out that reading just a single line of process
// output fixes the problem, but let's process
// all of the output, just in case.
br.lines().forEach(x -> {});
}
}
}
}
try {
if (!writeOutputToFile) {
code = p.waitFor();
}
return code;
} catch (InterruptedException ex) {
Log.verbose(ex);
throw new RuntimeException(ex);
}
}
static Executor of(String... cmdline) {
return new Executor().setCommandLine(cmdline);
}
static Executor of(ProcessBuilder pb) {
return new Executor().setProcessBuilder(pb);
}
private static String createLogMessage(ProcessBuilder pb) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("%s", pb.command()));
if (pb.directory() != null) {
sb.append(String.format("in %s", pb.directory().getAbsolutePath()));
}
return sb.toString();
}
private ProcessBuilder pb;
private boolean saveOutput;
private boolean writeOutputToFile;
private List<String> output;
private Consumer<Stream<String>> outputConsumer;
}