PageRenderTime 50ms CodeModel.GetById 35ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/src/net/sf/thingamablog/TimeoutInputStream.java

https://github.com/tixus/Thngm
Java | 439 lines | 298 code | 71 blank | 70 comment | 36 complexity | d03c6dedefbea35d62eecc694e5552d4 MD5 | raw file
  1package net.sf.thingamablog;
  2
  3
  4
  5import java.io.FilterInputStream;
  6import java.io.IOException;
  7import java.io.InputStream;
  8import java.io.InterruptedIOException;
  9
 10
 11/**
 12 * Wraps an input stream that blocks indefinitely to simulate timeouts on read(), skip(), and close(). The resulting
 13 * input stream is buffered and supports retrying operations that failed due to an InterruptedIOException. Supports
 14 * resuming partially completed operations after an InterruptedIOException REGARDLESS of whether the underlying stream
 15 * does unless the underlying stream itself generates InterruptedIOExceptions in which case it must also support
 16 * resuming. Check the bytesTransferred field to determine how much of the operation completed; conversely, at what
 17 * point to resume.
 18 */
 19
 20public class TimeoutInputStream extends FilterInputStream
 21{
 22
 23    private final long readTimeout;
 24    private final long closeTimeout;
 25    private boolean closeRequested = false;
 26    private Thread thread;
 27    private byte[] iobuffer;
 28    private int head = 0;
 29    private int length = 0;
 30    private IOException ioe = null;
 31    private boolean waitingForClose = false;
 32    private boolean growWhenFull = false;
 33
 34    /**
 35     * Creates a timeout wrapper for an input stream.
 36     * @param in the underlying input stream
 37     * @param bufferSize the buffer size in bytes; should be large enough to mitigate Thread synchronization and context
 38     * switching overhead
 39     * @param readTimeout the number of milliseconds to block for a read() or skip() before throwing an
 40     * InterruptedIOException;  blocks indefinitely
 41     * @param closeTimeout the number of milliseconds to block for a close() before throwing an InterruptedIOException;
 42     *  blocks indefinitely, -1 closes the stream in the background
 43     */
 44
 45    public TimeoutInputStream(InputStream in, int bufferSize, long readTimeout, long closeTimeout)
 46    {
 47        super(in);
 48        this.readTimeout = readTimeout;
 49        this.closeTimeout = closeTimeout;
 50        this.iobuffer = new byte[bufferSize];
 51
 52        thread = new Thread(new Runnable()
 53        {
 54            public void run()
 55            {
 56                runThread();
 57            }
 58
 59        }, "TimeoutInputStream");
 60
 61        thread.setDaemon(true);
 62        thread.start();
 63    }
 64
 65    public TimeoutInputStream(InputStream in, int bufferSize, long readTimeout, long closeTimeout, boolean growWhenFull)
 66    {
 67        this(in, bufferSize, readTimeout, closeTimeout);
 68        this.growWhenFull = growWhenFull;
 69    }
 70
 71    /**
 72     * Wraps the underlying stream's method. 
 73     * It may be important to wait for a stream to actually be closed because
 74     * it holds an implicit lock on a system resoure (such as a file) while it is  open. 
 75     * Closing a stream may take time if the underlying stream is still servicing a previous request. 
 76     * @throws InterruptedIOException if the timeout expired 
 77     * @throws IOException if an i/o error occurs 
 78     */
 79
 80    public void close() throws IOException
 81    {
 82        Thread oldThread;
 83
 84        synchronized(this)
 85        {
 86            if(thread == null)
 87                return;
 88
 89            oldThread = thread;
 90            closeRequested = true;
 91            thread.interrupt();
 92            checkError();
 93        }
 94
 95        if(closeTimeout == -1)
 96            return;
 97
 98        try
 99        {
100            oldThread.join(closeTimeout);
101        }
102        catch(InterruptedException e)
103        {
104            Thread.currentThread().interrupt();
105        }
106
107        synchronized(this)
108        {
109            checkError();
110
111            if(thread != null)
112                throw new InterruptedIOException();
113        }
114    }
115
116    /**
117     * Returns the number of unread bytes in the buffer. 
118     * @throws IOException if an i/o error occurs 
119     */
120
121    public synchronized int available() throws IOException
122    {
123        if(length == 0)
124            checkError();
125
126        return length > 0 ? length : 0;
127    }
128
129    /**
130     * Reads a byte from the stream. 
131     * @throws InterruptedIOException if the timeout expired and no data was received, 
132     * bytesTransferred will be zero
133     * 
134     * @throws IOException if an i/o error occurs 
135     */
136
137    public synchronized int read() throws IOException
138    {
139        if(!syncFill())
140            return -1;
141
142        int b = iobuffer[head++] & 255;
143
144        if(head == iobuffer.length)
145            head = 0;
146
147        length--;
148        notify();
149        return b;
150    }
151
152    /**
153     * Reads multiple bytes from the stream. 
154     * @throws InterruptedIOException if the timeout expired and no data was received, 
155     * bytesTransferred will be zero
156     * @throws IOException if an i/o error occurs 
157     */
158
159    public synchronized int read(byte[] buffer, int off, int len) throws IOException
160    {
161        if(!syncFill())
162            return -1;
163
164        int pos = off;
165        if(len > length)
166            len = length;
167
168        while(len-- > 0)
169        {
170            buffer[pos++] = iobuffer[head++];
171            if(head == iobuffer.length)
172                head = 0;
173            length--;
174        }
175
176        notify();
177        return pos - off;
178    }
179
180    /**
181     * Skips multiple bytes in the stream. 
182     * @throws InterruptedIOException if the timeout expired before all of the 
183     * bytes specified have been skipped,
184     * bytesTransferred may be non-zero 
185     * @throws IOException if an i/o error occurs 
186     */
187
188    public synchronized long skip(long count) throws IOException
189    {
190        long amount = 0;
191        try
192        {
193            do
194            {
195                if(!syncFill())
196                    break;
197
198                int skip = (int)Math.min(count - amount, length);
199                head = (head + skip) % iobuffer.length;
200                length -= skip;
201                amount += skip;
202            }
203            while(amount < count);
204        }
205        catch (InterruptedIOException e)
206        {
207            e.bytesTransferred = (int)amount;
208            throw e;
209        }
210
211        notify();
212        return amount;
213    }
214
215    /**
216     * Mark is not supported by the wrapper even if the underlying stream does, returns false. 
217     */
218
219    public boolean markSupported()
220    {
221        return false;
222    }
223
224    /**
225     *  Waits for the buffer to fill if it is empty and the stream has not reached EOF. 
226     * @return true if bytes are available, false if EOF has been reached 
227     * @throws InterruptedIOException if EOF not reached but no bytes are available 
228     */
229
230    private boolean syncFill() throws IOException
231    {
232        if(length != 0)
233            return true;
234
235        checkError();
236
237        if(waitingForClose)
238            return false;
239
240        notify();
241
242        try
243        {
244            wait(readTimeout);
245        }
246        catch(InterruptedException e)
247        {
248            Thread.currentThread().interrupt();
249        }
250
251        if(length != 0)
252            return true;
253
254        checkError();
255
256        if(waitingForClose)
257            return false;
258
259        throw new InterruptedIOException();
260    }
261
262    /**
263     * If an exception is pending, throw it. 
264     */
265    private void checkError() throws IOException
266    {
267        if(ioe != null)
268        {
269            IOException e = ioe;
270            ioe = null;
271            throw e;
272        }
273    }
274
275    /**
276     * Runs the thread in the background. 
277     */
278
279    private void runThread()
280    {
281        try
282        {
283            readUntilDone();
284        }
285        catch (IOException e)
286        {
287            synchronized(this)
288            {
289                ioe = e;
290            }
291        }
292        finally
293        {
294            waitUntilClosed();
295            try
296            {
297                in.close();
298            }
299            catch (IOException e)
300            {
301                synchronized(this)
302                {
303                    ioe = e;
304                }
305            }
306            finally
307            {
308                synchronized(this)
309                {
310                    thread = null;
311                    notify();
312                }
313            }
314        }
315    }
316
317    /**
318     * Waits until we have been requested to close the stream. 
319     */
320    private synchronized void waitUntilClosed()
321    {
322        waitingForClose = true;
323        notify();
324
325        while(!closeRequested)
326        {
327            try
328            {
329                wait();
330            }
331            catch (InterruptedException e)
332            {
333                closeRequested = true;
334            }
335        }
336    }
337
338    /**
339     * Reads bytes into the buffer until EOF, closed, or error. 
340     */
341
342    private void readUntilDone() throws IOException
343    {
344        for(;;)
345        {
346            int off;
347            int len;
348
349            synchronized(this)
350            {
351                while(isBufferFull())
352                {
353                    if(closeRequested)
354                        return;
355
356                    waitForRead();
357                }
358
359                off = (head + length) % iobuffer.length;
360                len = ((head > off) ? head : iobuffer.length) - off;
361            }
362
363            int count;
364            try
365            {
366                count = in.read(iobuffer, off, len);
367                if(count == -1)
368                    return;
369            }
370            catch(InterruptedIOException e)
371            {
372                count = e.bytesTransferred;
373            }
374
375            synchronized(this)
376            {
377                length += count;
378                notify();
379            }
380        }
381    }
382
383    private synchronized void waitForRead()
384    {
385        try
386        {
387            if(growWhenFull)
388            {
389                wait(readTimeout);
390            }
391            else
392            {
393                wait();
394            }
395        }
396        catch (InterruptedException e)
397        {
398            closeRequested = true;
399        }
400
401        if(growWhenFull && isBufferFull())
402        {
403            growBuffer();
404        }
405    }
406
407    private synchronized void growBuffer()
408    {
409        int newSize = 2 * iobuffer.length;
410        if(newSize > iobuffer.length)
411        {
412            if(true)
413            {
414                System.out.println("InputStream growing to " + newSize + " bytes");
415            }
416
417            byte[] newBuffer = new byte[newSize];
418
419            int pos = 0;
420            int len = length;
421
422            while(len-- > 0)
423            {
424                newBuffer[pos++] = iobuffer[head++];
425                if(head == iobuffer.length)
426                    head = 0;
427            }
428
429            iobuffer = newBuffer;
430            head = 0;
431        }
432    }
433
434    private boolean isBufferFull()
435    {
436        return length == iobuffer.length;
437    }
438
439}