2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
31 import java
.io
.IOException
;
32 import java
.io
.InputStream
;
35 * This class provides an optional millisecond timeout on its read()
36 * operations. This permits callers to bail out rather than block.
38 public class TimeoutInputStream
extends InputStream
{
40 // ------------------------------------------------------------------------
41 // Variables --------------------------------------------------------------
42 // ------------------------------------------------------------------------
47 private InputStream stream
;
50 * The timeout value in millis. If it takes longer than this for bytes
51 * to be available for read then a ReadTimeoutException is thrown. A
52 * value of 0 means to block as a normal InputStream would.
54 private int timeoutMillis
;
57 * If true, the current read() will timeout soon.
59 private volatile boolean cancel
= false;
61 // ------------------------------------------------------------------------
62 // Constructors -----------------------------------------------------------
63 // ------------------------------------------------------------------------
66 * Public constructor, at the default timeout of 10000 millis (10
69 * @param stream the wrapped InputStream
71 public TimeoutInputStream(final InputStream stream
) {
73 this.timeoutMillis
= 10000;
79 * @param stream the wrapped InputStream
80 * @param timeoutMillis the timeout value in millis. If it takes longer
81 * than this for bytes to be available for read then a
82 * ReadTimeoutException is thrown. A value of 0 means to block as a
83 * normal InputStream would.
85 public TimeoutInputStream(final InputStream stream
,
86 final int timeoutMillis
) {
88 if (timeoutMillis
< 0) {
89 throw new IllegalArgumentException("Invalid timeoutMillis value, " +
94 this.timeoutMillis
= timeoutMillis
;
97 // ------------------------------------------------------------------------
98 // InputStream ------------------------------------------------------------
99 // ------------------------------------------------------------------------
102 * Reads the next byte of data from the input stream.
104 * @return the next byte of data, or -1 if there is no more data because
105 * the end of the stream has been reached.
106 * @throws IOException if an I/O error occurs
109 public int read() throws IOException
{
111 if (timeoutMillis
== 0) {
112 // Block on the read().
113 return stream
.read();
116 if (stream
.available() > 0) {
117 // A byte is available now, return it.
118 return stream
.read();
121 // We will wait up to timeoutMillis to see if a byte is available.
122 // If not, we throw ReadTimeoutException.
123 long checkTime
= System
.currentTimeMillis();
124 while (stream
.available() == 0) {
125 long now
= System
.currentTimeMillis();
126 synchronized (this) {
127 if ((now
- checkTime
> timeoutMillis
) || (cancel
== true)) {
128 if (cancel
== true) {
131 throw new ReadTimeoutException("Timeout on read(): " +
132 (int) (now
- checkTime
) + " millis and still no data");
136 // How long do we sleep for, eh? For now we will go with 2
139 } catch (InterruptedException e
) {
144 if (stream
.available() > 0) {
145 // A byte is available now, return it.
146 return stream
.read();
149 throw new IOException("InputStream claimed a byte was available, but " +
150 "now it is not. What is going on?");
154 * Reads some number of bytes from the input stream and stores them into
155 * the buffer array b.
157 * @param b the buffer into which the data is read.
158 * @return the total number of bytes read into the buffer, or -1 if there
159 * is no more data because the end of the stream has been reached.
160 * @throws IOException if an I/O error occurs
163 public int read(final byte[] b
) throws IOException
{
164 if (timeoutMillis
== 0) {
165 // Block on the read().
166 return stream
.read(b
);
169 int remaining
= b
.length
;
171 if (stream
.available() >= remaining
) {
172 // Enough bytes are available now, return them.
173 return stream
.read(b
);
176 while (remaining
> 0) {
178 // We will wait up to timeoutMillis to see if a byte is
179 // available. If not, we throw ReadTimeoutException.
180 long checkTime
= System
.currentTimeMillis();
181 while (stream
.available() == 0) {
183 return (b
.length
- remaining
);
186 long now
= System
.currentTimeMillis();
187 synchronized (this) {
188 if ((now
- checkTime
> timeoutMillis
) || (cancel
== true)) {
189 if (cancel
== true) {
192 throw new ReadTimeoutException("Timeout on read(): " +
193 (int) (now
- checkTime
) + " millis and still no " +
198 // How long do we sleep for, eh? For now we will go with
201 } catch (InterruptedException e
) {
206 if (stream
.available() > 0) {
207 // At least one byte is available now, read it.
208 int n
= stream
.available();
212 int rc
= stream
.read(b
, b
.length
- remaining
, n
);
214 // This shouldn't happen.
215 throw new IOException("InputStream claimed bytes were " +
216 "available, but read() returned -1. What is going " +
220 if (remaining
== 0) {
226 throw new IOException("InputStream claimed all bytes were available, " +
227 "but now it is not. What is going on?");
231 * Reads up to len bytes of data from the input stream into an array of
234 * @param b the buffer into which the data is read.
235 * @param off the start offset in array b at which the data is written.
236 * @param len the maximum number of bytes to read.
237 * @return the total number of bytes read into the buffer, or -1 if there
238 * is no more data because the end of the stream has been reached.
239 * @throws IOException if an I/O error occurs
242 public int read(final byte[] b
, final int off
,
243 final int len
) throws IOException
{
245 if (timeoutMillis
== 0) {
246 // Block on the read().
247 return stream
.read(b
, off
, len
);
252 if (stream
.available() >= remaining
) {
253 // Enough bytes are available now, return them.
254 return stream
.read(b
, off
, remaining
);
257 while (remaining
> 0) {
259 // We will wait up to timeoutMillis to see if a byte is
260 // available. If not, we throw ReadTimeoutException.
261 long checkTime
= System
.currentTimeMillis();
262 while (stream
.available() == 0) {
264 return (len
- remaining
);
267 long now
= System
.currentTimeMillis();
268 synchronized (this) {
269 if ((now
- checkTime
> timeoutMillis
) || (cancel
== true)) {
270 if (cancel
== true) {
273 throw new ReadTimeoutException("Timeout on read(): " +
274 (int) (now
- checkTime
) + " millis and still no " +
279 // How long do we sleep for, eh? For now we will go with
282 } catch (InterruptedException e
) {
287 if (stream
.available() > 0) {
288 // At least one byte is available now, read it.
289 int n
= stream
.available();
293 int rc
= stream
.read(b
, off
+ len
- remaining
, n
);
295 // This shouldn't happen.
296 throw new IOException("InputStream claimed bytes were " +
297 "available, but read() returned -1. What is going " +
301 if (remaining
== 0) {
307 throw new IOException("InputStream claimed all bytes were available, " +
308 "but now it is not. What is going on?");
312 * Returns an estimate of the number of bytes that can be read (or
313 * skipped over) from this input stream without blocking by the next
314 * invocation of a method for this input stream.
316 * @return an estimate of the number of bytes that can be read (or
317 * skipped over) from this input stream without blocking or 0 when it
318 * reaches the end of the input stream.
319 * @throws IOException if an I/O error occurs
322 public int available() throws IOException
{
323 return stream
.available();
327 * Closes this input stream and releases any system resources associated
330 * @throws IOException if an I/O error occurs
333 public void close() throws IOException
{
338 * Marks the current position in this input stream.
340 * @param readLimit the maximum limit of bytes that can be read before
341 * the mark position becomes invalid
344 public void mark(final int readLimit
) {
345 stream
.mark(readLimit
);
349 * Tests if this input stream supports the mark and reset methods.
351 * @return true if this stream instance supports the mark and reset
352 * methods; false otherwise
355 public boolean markSupported() {
356 return stream
.markSupported();
360 * Repositions this stream to the position at the time the mark method
361 * was last called on this input stream.
363 * @throws IOException if an I/O error occurs
366 public void reset() throws IOException
{
371 * Skips over and discards n bytes of data from this input stream.
373 * @param n the number of bytes to be skipped
374 * @return the actual number of bytes skipped
375 * @throws IOException if an I/O error occurs
378 public long skip(final long n
) throws IOException
{
379 return stream
.skip(n
);
382 // ------------------------------------------------------------------------
383 // TimeoutInputStream -----------------------------------------------------
384 // ------------------------------------------------------------------------
387 * Request that the current read() operation timeout immediately.
389 public synchronized void cancelRead() {