ProcessReader.java
/*
* This file is part of dependency-check-utils.
*
* 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.
*
* Copyright (c) 2020 Jeremy Long. All Rights Reserved.
*/
package org.owasp.dependencycheck.utils.processing;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
/**
* Utility to read the output from a `Process` and places the output into
* provide storage containers.
*
* @author Jeremy Long
*/
public class ProcessReader implements AutoCloseable {
/**
* A reference to the process that will be read.
*/
private final Process process;
/**
* Reader for the error stream.
*/
private Gobbler errorGobbler = null;
/**
* Reader for the input stream.
*/
private Gobbler inputGobbler = null;
/**
* Processor for the input stream.
*/
private Processor<InputStream> processor = null;
/**
* A list of threads that were started.
*/
private final List<Thread> threads = new ArrayList<>();
/**
* Creates a new reader for the given process. The output from the process
* is written to the provided stores.
*
* @param process the process to read from
*/
public ProcessReader(Process process) {
this(process, null);
}
/**
* Creates a new reader for the given process. The output from the process
* is written to the provided stores.
*
* @param process the process to read from
* @param processor used to process the input stream from the process
*/
public ProcessReader(Process process, Processor<InputStream> processor) {
this.process = process;
this.processor = processor;
}
/**
* Returns the error stream output from the process.
*
* @return the error stream output
*/
public String getError() {
return errorGobbler.getText();
}
/**
* Returns the output from standard out from the process.
*
* @return the output from standard out from the process
*/
public String getOutput() {
return inputGobbler != null ? inputGobbler.getText() : null;
}
/**
* Reads the standard output and standard error from the process and waits
* for the process to complete.
*
* @throws InterruptedException thrown if the processing threads are
* interrupted
* @throws IOException thrown if there is an error reading from the process
*/
public void readAll() throws InterruptedException, IOException {
start();
close();
}
/**
* Starts the processing of the `process`.
*/
private void start() {
errorGobbler = new Gobbler(process.getErrorStream());
startProcessor(errorGobbler);
if (processor == null) {
inputGobbler = new Gobbler(process.getInputStream());
startProcessor(inputGobbler);
} else {
processor.setInput(process.getInputStream());
startProcessor(processor);
}
}
/**
* Starts the process in its own thread and collects the threads so `join`
* can be called later to ensure the thread finishes.
*
* @param p a reference to the processor to start.
*/
private void startProcessor(Processor p) {
if (p != null) {
final Thread t = new Thread(p);
threads.add(t);
t.start();
}
}
/**
* Waits for the process and related threads to complete.
*
* @throws InterruptedException thrown if the processing threads are
* interrupted
* @throws IOException thrown if there was an error reading from the process
*/
@Override
public void close() throws InterruptedException, IOException {
if (process.isAlive()) {
process.waitFor();
}
if (threads.size() > 0) {
for (Thread thread : threads) {
thread.join();
}
threads.clear();
}
errorGobbler.close();
if (inputGobbler != null) {
inputGobbler.close();
}
}
static class Gobbler extends Processor<InputStream> {
/**
* A store for an exception - if one is thrown during processing.
*/
private IOException exception;
/**
* A store for the text read from the input stream.
*/
private String text;
Gobbler(InputStream inputStream) {
super(inputStream);
}
@Override
public void run() {
try {
final InputStream inputStream = getInput();
text = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
} catch (IOException ex) {
exception = ex;
}
}
public String getText() {
return text;
}
@Override
public void close() throws IOException {
if (exception != null) {
throw exception;
}
}
}
}