PageRenderTime 55ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

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