// Copyright 2014 The Bazel Authors. 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 com.google.devtools.build.lib.shell; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Provides sinks for input streams. Continuously read an input stream * until the end-of-file is encountered. The stream may be redirected to * an {@link OutputStream}, or discarded. * <p> * This class is useful for handing the {@code stdout} and {@code stderr} * streams from a {@link Process} started with {@link Runtime#exec(String)}. * If these streams are not consumed, the Process may block resulting in a * deadlock. * * @see <a href="http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html"> * JavaWorld: When Runtime.exec() won't</a> */ public final class InputStreamSink { /** * Black hole into which bytes are sometimes discarded by {@link NullSink}. * It is shared by all threads since the actual contents of the buffer * are irrelevant. */ private static final byte[] DISCARD = new byte[4096]; // Supresses default constructor; ensures non-instantiability private InputStreamSink() { } /** * A {@link Thread} which reads and discards data from an * {@link InputStream}. */ private static class NullSink implements Runnable { private final InputStream in; public NullSink(InputStream in) { this.in = in; } @Override public void run() { try { try { // Attempt to just skip all input do { in.skip(Integer.MAX_VALUE); } while (in.read() != -1); // Need to test for EOF } catch (IOException ioe) { // Some streams throw IOException when skip() is called; // resort to reading off all input with read(): while (in.read(DISCARD) != -1) { // no loop body } } } catch (IOException e) { throw new RuntimeException(e); } } } /** * A {@link Thread} which reads data from an {@link InputStream}, * and translates it into an {@link OutputStream}. */ private static class CopySink implements Runnable { private final InputStream in; private final OutputStream out; public CopySink(InputStream in, OutputStream out) { this.in = in; this.out = out; } @Override public void run() { try { byte[] buffer = new byte[2048]; int bytesRead; while ((bytesRead = in.read(buffer)) >= 0) { out.write(buffer, 0, bytesRead); out.flush(); } } catch (IOException e) { throw new RuntimeException(e); } } } /** * Creates a {@link Runnable} which consumes the provided * {@link InputStream} 'in', discarding its contents. */ public static Runnable newRunnableSink(InputStream in) { if (in == null) { throw new NullPointerException("in"); } return new NullSink(in); } /** * Creates a {@link Runnable} which copies everything from 'in' * to 'out'. 'out' will be written to and flushed after each * read from 'in'. However, 'out' will not be closed. */ public static Runnable newRunnableSink(InputStream in, OutputStream out) { if (in == null) { throw new NullPointerException("in"); } if (out == null) { throw new NullPointerException("out"); } return new CopySink(in, out); } }