#16 Refactor Swing backend, demo of multiple TApplications in one Swing frame
[nikiroo-utils.git] / src / jexer / io / TimeoutInputStream.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2017 Kevin Lamonte
7 *
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:
14 *
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
17 *
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.
25 *
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
27 * @version 1
28 */
29 package jexer.io;
30
31 import java.io.IOException;
32 import java.io.InputStream;
33
34 /**
35 * This class provides an optional millisecond timeout on its read()
36 * operations. This permits callers to bail out rather than block.
37 */
38 public class TimeoutInputStream extends InputStream {
39
40 /**
41 * The wrapped stream.
42 */
43 private InputStream stream;
44
45 /**
46 * The timeout value in millis. If it takes longer than this for bytes
47 * to be available for read then a ReadTimeoutException is thrown. A
48 * value of 0 means to block as a normal InputStream would.
49 */
50 private int timeoutMillis;
51
52 /**
53 * If true, the current read() will timeout soon.
54 */
55 private volatile boolean cancel = false;
56
57 /**
58 * Request that the current read() operation timeout immediately.
59 */
60 public synchronized void cancelRead() {
61 cancel = true;
62 }
63
64 /**
65 * Public constructor, at the default timeout of 10000 millis (10
66 * seconds).
67 *
68 * @param stream the wrapped InputStream
69 */
70 public TimeoutInputStream(final InputStream stream) {
71 this.stream = stream;
72 this.timeoutMillis = 10000;
73 }
74
75 /**
76 * Public constructor.
77 *
78 * @param stream the wrapped InputStream
79 * @param timeoutMillis the timeout value in millis. If it takes longer
80 * than this for bytes to be available for read then a
81 * ReadTimeoutException is thrown. A value of 0 means to block as a
82 * normal InputStream would.
83 */
84 public TimeoutInputStream(final InputStream stream,
85 final int timeoutMillis) {
86
87 if (timeoutMillis < 0) {
88 throw new IllegalArgumentException("Invalid timeoutMillis value, " +
89 "must be >= 0");
90 }
91
92 this.stream = stream;
93 this.timeoutMillis = timeoutMillis;
94 }
95
96 /**
97 * Reads the next byte of data from the input stream.
98 *
99 * @return the next byte of data, or -1 if there is no more data because
100 * the end of the stream has been reached.
101 * @throws IOException if an I/O error occurs
102 */
103 @Override
104 public int read() throws IOException {
105
106 if (timeoutMillis == 0) {
107 // Block on the read().
108 return stream.read();
109 }
110
111 if (stream.available() > 0) {
112 // A byte is available now, return it.
113 return stream.read();
114 }
115
116 // We will wait up to timeoutMillis to see if a byte is available.
117 // If not, we throw ReadTimeoutException.
118 long checkTime = System.currentTimeMillis();
119 while (stream.available() == 0) {
120 long now = System.currentTimeMillis();
121 synchronized (this) {
122 if ((now - checkTime > timeoutMillis) || (cancel == true)) {
123 if (cancel == true) {
124 cancel = false;
125 }
126 throw new ReadTimeoutException("Timeout on read(): " +
127 (int) (now - checkTime) + " millis and still no data");
128 }
129 }
130 try {
131 // How long do we sleep for, eh? For now we will go with 2
132 // millis.
133 Thread.sleep(2);
134 } catch (InterruptedException e) {
135 // SQUASH
136 }
137 }
138
139 if (stream.available() > 0) {
140 // A byte is available now, return it.
141 return stream.read();
142 }
143
144 throw new IOException("InputStream claimed a byte was available, but " +
145 "now it is not. What is going on?");
146 }
147
148 /**
149 * Reads some number of bytes from the input stream and stores them into
150 * the buffer array b.
151 *
152 * @param b the buffer into which the data is read.
153 * @return the total number of bytes read into the buffer, or -1 if there
154 * is no more data because the end of the stream has been reached.
155 * @throws IOException if an I/O error occurs
156 */
157 @Override
158 public int read(final byte[] b) throws IOException {
159 if (timeoutMillis == 0) {
160 // Block on the read().
161 return stream.read(b);
162 }
163
164 int remaining = b.length;
165
166 if (stream.available() >= remaining) {
167 // Enough bytes are available now, return them.
168 return stream.read(b);
169 }
170
171 while (remaining > 0) {
172
173 // We will wait up to timeoutMillis to see if a byte is
174 // available. If not, we throw ReadTimeoutException.
175 long checkTime = System.currentTimeMillis();
176 while (stream.available() == 0) {
177 long now = System.currentTimeMillis();
178
179 synchronized (this) {
180 if ((now - checkTime > timeoutMillis) || (cancel == true)) {
181 if (cancel == true) {
182 cancel = false;
183 }
184 throw new ReadTimeoutException("Timeout on read(): " +
185 (int) (now - checkTime) + " millis and still no " +
186 "data");
187 }
188 }
189 try {
190 // How long do we sleep for, eh? For now we will go with
191 // 2 millis.
192 Thread.sleep(2);
193 } catch (InterruptedException e) {
194 // SQUASH
195 }
196 }
197
198 if (stream.available() > 0) {
199 // At least one byte is available now, read it.
200 int n = stream.available();
201 if (remaining < n) {
202 n = remaining;
203 }
204 int rc = stream.read(b, b.length - remaining, n);
205 if (rc == -1) {
206 // This shouldn't happen.
207 throw new IOException("InputStream claimed bytes were " +
208 "available, but read() returned -1. What is going " +
209 "on?");
210 }
211 remaining -= rc;
212 return rc;
213 }
214 }
215
216 throw new IOException("InputStream claimed all bytes were available, " +
217 "but now it is not. What is going on?");
218 }
219
220 /**
221 * Reads up to len bytes of data from the input stream into an array of
222 * bytes.
223 *
224 * @param b the buffer into which the data is read.
225 * @param off the start offset in array b at which the data is written.
226 * @param len the maximum number of bytes to read.
227 * @return the total number of bytes read into the buffer, or -1 if there
228 * is no more data because the end of the stream has been reached.
229 * @throws IOException if an I/O error occurs
230 */
231 @Override
232 public int read(final byte[] b, final int off,
233 final int len) throws IOException {
234
235 if (timeoutMillis == 0) {
236 // Block on the read().
237 return stream.read(b);
238 }
239
240 int remaining = len;
241
242 if (stream.available() >= remaining) {
243 // Enough bytes are available now, return them.
244 return stream.read(b, off, remaining);
245 }
246
247 while (remaining > 0) {
248
249 // We will wait up to timeoutMillis to see if a byte is
250 // available. If not, we throw ReadTimeoutException.
251 long checkTime = System.currentTimeMillis();
252 while (stream.available() == 0) {
253 long now = System.currentTimeMillis();
254 synchronized (this) {
255 if ((now - checkTime > timeoutMillis) || (cancel == true)) {
256 if (cancel == true) {
257 cancel = false;
258 }
259 throw new ReadTimeoutException("Timeout on read(): " +
260 (int) (now - checkTime) + " millis and still no " +
261 "data");
262 }
263 }
264 try {
265 // How long do we sleep for, eh? For now we will go with
266 // 2 millis.
267 Thread.sleep(2);
268 } catch (InterruptedException e) {
269 // SQUASH
270 }
271 }
272
273 if (stream.available() > 0) {
274 // At least one byte is available now, read it.
275 int n = stream.available();
276 if (remaining < n) {
277 n = remaining;
278 }
279 int rc = stream.read(b, off + len - remaining, n);
280 if (rc == -1) {
281 // This shouldn't happen.
282 throw new IOException("InputStream claimed bytes were " +
283 "available, but read() returned -1. What is going " +
284 "on?");
285 }
286 return rc;
287 }
288 }
289
290 throw new IOException("InputStream claimed all bytes were available, " +
291 "but now it is not. What is going on?");
292 }
293
294 /**
295 * Returns an estimate of the number of bytes that can be read (or
296 * skipped over) from this input stream without blocking by the next
297 * invocation of a method for this input stream.
298 *
299 * @return an estimate of the number of bytes that can be read (or
300 * skipped over) from this input stream without blocking or 0 when it
301 * reaches the end of the input stream.
302 * @throws IOException if an I/O error occurs
303 */
304 @Override
305 public int available() throws IOException {
306 return stream.available();
307 }
308
309 /**
310 * Closes this input stream and releases any system resources associated
311 * with the stream.
312 *
313 * @throws IOException if an I/O error occurs
314 */
315 @Override
316 public void close() throws IOException {
317 stream.close();
318 }
319
320 /**
321 * Marks the current position in this input stream.
322 *
323 * @param readLimit the maximum limit of bytes that can be read before
324 * the mark position becomes invalid
325 */
326 @Override
327 public void mark(final int readLimit) {
328 stream.mark(readLimit);
329 }
330
331 /**
332 * Tests if this input stream supports the mark and reset methods.
333 *
334 * @return true if this stream instance supports the mark and reset
335 * methods; false otherwise
336 */
337 @Override
338 public boolean markSupported() {
339 return stream.markSupported();
340 }
341
342 /**
343 * Repositions this stream to the position at the time the mark method
344 * was last called on this input stream.
345 *
346 * @throws IOException if an I/O error occurs
347 */
348 @Override
349 public void reset() throws IOException {
350 stream.reset();
351 }
352
353 /**
354 * Skips over and discards n bytes of data from this input stream.
355 *
356 * @param n the number of bytes to be skipped
357 * @return the actual number of bytes skipped
358 * @throws IOException if an I/O error occurs
359 */
360 @Override
361 public long skip(final long n) throws IOException {
362 return stream.skip(n);
363 }
364
365 }