001 package com.thaiopensource.validate.auto; 002 003 import java.io.InputStream; 004 import java.io.IOException; 005 006 public class RewindableInputStream extends InputStream implements Rewindable { 007 static class Block { 008 Block next; 009 final byte[] buf; 010 int used = 0; 011 static final int MIN_SIZE = 1024; 012 Block(int minSize) { 013 buf = new byte[Math.max(MIN_SIZE, minSize)]; 014 } 015 016 Block() { 017 this(0); 018 } 019 020 void append(byte b) { 021 buf[used++] = b; 022 } 023 024 void append(byte[] b, int off, int len) { 025 System.arraycopy(b, off, buf, used, len); 026 used += len; 027 } 028 } 029 030 private Block head; 031 /** 032 * If curBlockAvail > 0, then there are curBlockAvail bytes available to be 033 * returned starting at curBlockPos in curBlock.buf. 034 */ 035 private int curBlockAvail; 036 private Block curBlock; 037 private int curBlockPos; 038 private Block lastBlock; 039 /** 040 * true unless willNotRewind has been called 041 */ 042 private boolean saving = true; 043 private final InputStream in; 044 private boolean pretendClosed = false; 045 /** 046 * true if we have got an EOF from the underlying InputStream 047 */ 048 private boolean eof; 049 050 public RewindableInputStream(InputStream in) { 051 if (in == null) 052 throw new NullPointerException(); 053 this.in = in; 054 } 055 056 public void close() throws IOException { 057 if (saving) { 058 curBlockAvail = 0; 059 curBlock = null; 060 pretendClosed = true; 061 } 062 else { 063 head = null; 064 curBlock = null; 065 lastBlock = null; 066 saving = false; 067 curBlockAvail = 0; 068 in.close(); 069 } 070 } 071 072 public void rewind() { 073 if (!saving) 074 throw new IllegalStateException("rewind() after willNotRewind()"); 075 pretendClosed = false; 076 if (head == null) 077 return; 078 curBlock = head; 079 curBlockPos = 0; 080 curBlockAvail = curBlock.used; 081 } 082 083 public boolean canRewind() { 084 return saving; 085 } 086 087 public void willNotRewind() { 088 saving = false; 089 head = null; 090 lastBlock = null; 091 if (pretendClosed) { 092 pretendClosed = false; 093 try { 094 in.close(); 095 } 096 catch (IOException e) { } 097 } 098 } 099 100 public int read() throws IOException { 101 if (curBlockAvail > 0) { 102 int c = curBlock.buf[curBlockPos++] & 0xFF; 103 --curBlockAvail; 104 if (curBlockAvail == 0) { 105 curBlock = curBlock.next; 106 if (curBlock != null) { 107 curBlockPos = 0; 108 curBlockAvail = curBlock.used; 109 } 110 } 111 return c; 112 } 113 int c = in.read(); 114 if (saving && c != -1) { 115 if (lastBlock == null) 116 lastBlock = head = new Block(); 117 else if (lastBlock.used == lastBlock.buf.length) 118 lastBlock = lastBlock.next = new Block(); 119 lastBlock.append((byte)c); 120 } 121 return c; 122 } 123 124 public int read(byte b[], int off, int len) throws IOException { 125 if (curBlockAvail == 0 && !saving) 126 return in.read(b, off, len); 127 if (b == null) 128 throw new NullPointerException(); 129 if (len < 0) 130 throw new IndexOutOfBoundsException(); 131 int nRead = 0; 132 if (curBlockAvail != 0) { 133 for (;;) { 134 if (len == 0) 135 return nRead; 136 b[off++] = curBlock.buf[curBlockPos++]; 137 --len; 138 nRead++; 139 --curBlockAvail; 140 if (curBlockAvail == 0) { 141 curBlock = curBlock.next; 142 if (curBlock == null) 143 break; 144 curBlockAvail = curBlock.used; 145 curBlockPos = 0; 146 } 147 } 148 } 149 if (len == 0) 150 return nRead; 151 if (eof) 152 return nRead > 0 ? nRead : -1; 153 try { 154 int n = in.read(b, off, len); 155 if (n < 0) { 156 eof = true; 157 return nRead > 0 ? nRead : -1; 158 } 159 nRead += n; 160 if (saving) { 161 if (lastBlock == null) 162 lastBlock = head = new Block(n); 163 else if (lastBlock.buf.length - lastBlock.used < n) { 164 if (lastBlock.used != lastBlock.buf.length) { 165 int free = lastBlock.buf.length - lastBlock.used; 166 lastBlock.append(b, off, free); 167 off += free; 168 n -= free; 169 } 170 lastBlock = lastBlock.next = new Block(n); 171 } 172 lastBlock.append(b, off, n); 173 } 174 } 175 catch (IOException e) { 176 eof = true; 177 if (nRead == 0) 178 throw e; 179 } 180 return nRead; 181 } 182 183 public int available() throws IOException { 184 if (curBlockAvail == 0) 185 return in.available(); 186 int n = curBlockAvail; 187 for (Block b = curBlock.next; b != null; b = b.next) 188 n += b.used; 189 return n + in.available(); 190 } 191 192 }