aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/shell/InputStreamSink.java
blob: 8e8f54f49240f7bf0ec566962bbb7d6108cfcc19 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// 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&apos;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);
  }
}