
io.helidon.build.util.ProcessMonitor Maven / Gradle / Ivy
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.helidon.build.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNullElseGet;
/**
* Executes a process and waits for completion, monitoring the output.
*/
public final class ProcessMonitor {
private static final ExecutorService EXECUTOR = ForkJoinPool.commonPool();
private final ProcessBuilder builder;
private final String description;
private final boolean capturing;
private final List capturedOutput;
private final List capturedStdOut;
private final List capturedStdErr;
private final Consumer monitorOut;
private final Consumer stdOut;
private final Consumer stdErr;
private final Predicate filter;
private final Function transform;
private int exitCode;
/**
* Returns a new builder.
*
* @return The builder.
*/
public static Builder builder() {
return new Builder();
}
/**
* Builder for a {@link ProcessMonitor}.
*/
public static final class Builder {
private ProcessBuilder builder;
private String description;
private boolean capture;
private Consumer monitorOut;
private Consumer stdOut;
private Consumer stdErr;
private Predicate filter;
private Function transform;
private Builder() {
}
/**
* Sets the process builder.
*
* @param processBuilder The process builder.
* @return This builder.
*/
public Builder processBuilder(ProcessBuilder processBuilder) {
this.builder = processBuilder;
return this;
}
/**
* Sets the process description.
*
* @param description The description.
* @return This builder.
*/
public Builder description(String description) {
this.description = description;
return this;
}
/**
* Sets whether or not to capture output.
*
* @param capture {@code true} if output should be capturee.
* @return This builder.
*/
public Builder capture(boolean capture) {
this.capture = capture;
return this;
}
/**
* Sets the consumer for process {@code stdout} stream.
*
* @param stdOut The description.
* @return This builder.
*/
public Builder stdOut(Consumer stdOut) {
this.stdOut = stdOut;
return this;
}
/**
* Sets the consumer for process {@code stderr} stream.
*
* @param stdErr The description.
* @return This builder.
*/
public Builder stdErr(Consumer stdErr) {
this.stdErr = stdErr;
return this;
}
/**
* Sets a filter for all process output.
*
* @param filter The filter.
* @return This builder.
*/
public Builder filter(Predicate filter) {
this.filter = filter;
return this;
}
/**
* Sets a transformer for all process output.
*
* @param transform The transformer.
* @return This builder.
*/
public Builder transform(Function transform) {
this.transform = transform;
return this;
}
/**
* Builds the instance.
*
* @return The instance.
*/
public ProcessMonitor build() {
if (builder == null) {
throw new IllegalStateException("processBuilder required");
}
monitorOut = stdOut;
if (stdOut == null) {
capture = true;
stdOut = ProcessMonitor::devNull;
monitorOut = Log::info;
}
if (stdErr == null) {
capture = true;
stdErr = ProcessMonitor::devNull;
}
if (filter == null) {
filter = line -> true;
}
if (transform == null) {
transform = Function.identity();
}
return new ProcessMonitor(this);
}
}
private ProcessMonitor(Builder builder) {
this.builder = builder.builder;
this.description = builder.description;
this.capturing = builder.capture;
this.monitorOut = builder.monitorOut;
this.stdOut = builder.stdOut;
this.stdErr = builder.stdErr;
this.capturedOutput = capturing ? new ArrayList<>() : emptyList();
this.capturedStdOut = capturing ? new ArrayList<>() : emptyList();
this.capturedStdErr = capturing ? new ArrayList<>() : emptyList();
this.filter = builder.filter;
this.transform = builder.transform;
}
/**
* Executes the process and waits for completion.
*
* @return This instance.
* @throws IOException If the process fails.
* @throws InterruptedException If the a thread is interrupted.
*/
public ProcessMonitor execute() throws IOException, InterruptedException {
if (description != null) {
monitorOut.accept(description);
}
final Process process = builder.start();
final Future> out = monitor(process.getInputStream(), filter, transform, capturing ? this::captureStdOut : stdOut);
final Future> err = monitor(process.getErrorStream(), filter, transform, capturing ? this::captureStdErr : stdErr);
exitCode = process.waitFor();
out.cancel(true);
err.cancel(true);
if (exitCode != 0) {
throw new ProcessFailedException(this);
}
return this;
}
/**
* Returns the combined captured output.
*
* @return The output. Empty if capture not enabled.
*/
public List output() {
return capturedOutput;
}
/**
* Returns any captured stderr output.
*
* @return The output. Empty if capture not enabled.
*/
public List stdOut() {
return capturedStdOut;
}
/**
* Returns any captured stderr output.
*
* @return The output. Empty if capture not enabled.
*/
public List stdErr() {
return capturedStdErr;
}
/**
* Process failed exception.
*/
public static final class ProcessFailedException extends IOException {
private final ProcessMonitor monitor;
private ProcessFailedException(ProcessMonitor monitor) {
this.monitor = monitor;
}
/**
* Returns the process monitor.
*
* @return The monitor.
*/
public ProcessMonitor processMonitor() {
return monitor;
}
@Override
public String getMessage() {
return monitor.toErrorMessage();
}
}
private String toErrorMessage() {
final StringBuilder message = new StringBuilder();
message.append(requireNonNullElseGet(description, () -> String.join(" ", builder.command())));
message.append(" FAILED with exit code ").append(exitCode);
if (capturing) {
message.append(Constants.EOL);
capturedOutput.forEach(line -> message.append(" ").append(line).append(Constants.EOL));
}
return message.toString();
}
private static void devNull(String line) {
}
private void captureStdOut(String line) {
stdOut.accept(line);
synchronized (capturedOutput) {
capturedOutput.add(line);
capturedStdOut.add(line);
}
}
private void captureStdErr(String line) {
stdErr.accept(line);
synchronized (capturedOutput) {
capturedOutput.add(line);
capturedStdErr.add(line);
}
}
private static Future> monitor(InputStream input,
Predicate filter,
Function transform,
Consumer output) {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
return EXECUTOR.submit(() -> reader.lines().forEach(line -> {
if (filter.test(line)) {
output.accept(transform.apply(line));
}
}));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy