package org.embulk.spi.util; import java.io.InputStream; import java.io.IOException; public class ResumableInputStream extends InputStream { public interface Reopener { public InputStream reopen(long offset, Exception closedCause) throws IOException; } private final Reopener reopener; protected InputStream in; private long offset; private long markedOffset; private Exception lastClosedCause; private boolean closed; public ResumableInputStream(InputStream initialInputStream, Reopener reopener) { this.reopener = reopener; this.in = initialInputStream; this.offset = 0L; this.markedOffset = 0L; this.lastClosedCause = null; } public ResumableInputStream(Reopener reopener) throws IOException { this(reopener.reopen(0, null), reopener); } private void reopen(Exception closedCause) throws IOException { if (in != null) { lastClosedCause = closedCause; try { in.close(); } catch (IOException ignored) { } in = null; } in = reopener.reopen(offset, closedCause); lastClosedCause = null; } @Override public int read() throws IOException { ensureOpened(); while (true) { try { int v = in.read(); offset += 1; return v; } catch (IOException | RuntimeException ex) { reopen(ex); } } } @Override public int read(byte[] b) throws IOException { ensureOpened(); while (true) { try { int r = in.read(b); offset += r; return r; } catch (IOException | RuntimeException ex) { reopen(ex); } } } @Override public int read(byte[] b, int off, int len) throws IOException { ensureOpened(); while (true) { try { int r = in.read(b, off, len); offset += r; return r; } catch (IOException | RuntimeException ex) { reopen(ex); } } } @Override public long skip(long n) throws IOException { ensureOpened(); while (true) { try { long r = in.skip(n); offset += r; return r; } catch (IOException | RuntimeException ex) { reopen(ex); } } } @Override public int available() throws IOException { ensureOpened(); return in.available(); } @Override public void close() throws IOException { if (in != null) { in.close(); closed = true; in = null; } } @Override public void mark(int readlimit) { try { ensureOpened(); } catch (IOException ex) { throw new RuntimeException(ex); } in.mark(readlimit); markedOffset = offset; } @Override public void reset() throws IOException { ensureOpened(); in.reset(); offset = markedOffset; } @Override public boolean markSupported() { try { ensureOpened(); } catch (IOException ex) { throw new RuntimeException(ex); } return in.markSupported(); } private void ensureOpened() throws IOException { if (in == null) { if (closed) { throw new IOException("stream closed"); } reopen(lastClosedCause); } } }