ResumableInputStream extends InputStream { @Override public long skip(long n) throws IOException { while (true) { try { long r = in.skip(n); if (r > 0) { offset += r; } return r; } catch (IOException | RuntimeException ex) { reopen(ex); } } } ResumableInputStream(InputStream initialInputStream, Reopener reopener);  ResumableInputStream(Reopener reopener); @Override int read(); @Override int read(byte[] b); @Override int read(byte[] b, int off, int len); @Override long skip(long n); @Override int available(); @Override void close();  }
@Test public void skipSkipsFromNext() throws Exception { ResumableInputStream in = new ResumableInputStream(new TestingInputStream(1, 2, -1, -1), new SimpleReopener(null)); assertThat(in.skip(1), is(1L)); assertThat(in.skip(1), is(2L)); assertThat(in.skip(1), is(-1L)); assertThat(in.skip(1), is(-1L)); }
@Test public void skipResumesWithRuntimeException() throws Exception { TestingInputStream next = new TestingInputStream( RUNTIME_EXCEPTION, RUNTIME_EXCEPTION, RUNTIME_EXCEPTION, RUNTIME_EXCEPTION, RUNTIME_EXCEPTION, 1, IO_EXCEPTION, 2, -1); SimpleReopener reopener = new SimpleReopener(next); ResumableInputStream in = new ResumableInputStream(next, reopener); assertThat(in.skip(1), is(1L)); assertThat(reopener.getLastOffset(), is(0L)); assertThat(reopener.getLastClosedCause(), instanceOf(RuntimeException.class)); assertThat(reopener.getReopenCount(), is(5)); assertThat(in.skip(1), is(2L)); assertThat(reopener.getLastOffset(), is(1L)); assertThat(reopener.getReopenCount(), is(6)); assertThat(reopener.getLastClosedCause(), instanceOf(IOException.class)); assertThat(in.skip(1), is(-1L)); assertThat(reopener.getLastOffset(), is(1L)); assertThat(reopener.getReopenCount(), is(6)); }