blob: 8e8f54f49240f7bf0ec566962bbb7d6108cfcc19 [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.shell;
16
17import java.io.IOException;
18import java.io.InputStream;
19import java.io.OutputStream;
20
21/**
22 * Provides sinks for input streams. Continuously read an input stream
23 * until the end-of-file is encountered. The stream may be redirected to
24 * an {@link OutputStream}, or discarded.
25 * <p>
26 * This class is useful for handing the {@code stdout} and {@code stderr}
27 * streams from a {@link Process} started with {@link Runtime#exec(String)}.
28 * If these streams are not consumed, the Process may block resulting in a
29 * deadlock.
30 *
31 * @see <a href="http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html">
32 * JavaWorld: When Runtime.exec() won&apos;t</a>
33 */
34public final class InputStreamSink {
35
36 /**
37 * Black hole into which bytes are sometimes discarded by {@link NullSink}.
38 * It is shared by all threads since the actual contents of the buffer
39 * are irrelevant.
40 */
41 private static final byte[] DISCARD = new byte[4096];
42
43 // Supresses default constructor; ensures non-instantiability
44 private InputStreamSink() {
45 }
46
47 /**
48 * A {@link Thread} which reads and discards data from an
49 * {@link InputStream}.
50 */
51 private static class NullSink implements Runnable {
52 private final InputStream in;
53
54 public NullSink(InputStream in) {
55 this.in = in;
56 }
57
58 @Override
59 public void run() {
60 try {
61 try {
62 // Attempt to just skip all input
63 do {
64 in.skip(Integer.MAX_VALUE);
65 } while (in.read() != -1); // Need to test for EOF
66 } catch (IOException ioe) {
67 // Some streams throw IOException when skip() is called;
68 // resort to reading off all input with read():
69 while (in.read(DISCARD) != -1) {
70 // no loop body
71 }
72 }
73 } catch (IOException e) {
74 throw new RuntimeException(e);
75 }
76 }
77 }
78
79 /**
80 * A {@link Thread} which reads data from an {@link InputStream},
81 * and translates it into an {@link OutputStream}.
82 */
83 private static class CopySink implements Runnable {
84
85 private final InputStream in;
86 private final OutputStream out;
87
88 public CopySink(InputStream in, OutputStream out) {
89 this.in = in;
90 this.out = out;
91 }
92
93 @Override
94 public void run() {
95 try {
96 byte[] buffer = new byte[2048];
97 int bytesRead;
98 while ((bytesRead = in.read(buffer)) >= 0) {
99 out.write(buffer, 0, bytesRead);
100 out.flush();
101 }
102 } catch (IOException e) {
103 throw new RuntimeException(e);
104 }
105 }
106 }
107
108 /**
109 * Creates a {@link Runnable} which consumes the provided
110 * {@link InputStream} 'in', discarding its contents.
111 */
112 public static Runnable newRunnableSink(InputStream in) {
113 if (in == null) {
114 throw new NullPointerException("in");
115 }
116 return new NullSink(in);
117 }
118
119 /**
120 * Creates a {@link Runnable} which copies everything from 'in'
121 * to 'out'. 'out' will be written to and flushed after each
122 * read from 'in'. However, 'out' will not be closed.
123 */
124 public static Runnable newRunnableSink(InputStream in, OutputStream out) {
125 if (in == null) {
126 throw new NullPointerException("in");
127 }
128 if (out == null) {
129 throw new NullPointerException("out");
130 }
131 return new CopySink(in, out);
132 }
133}