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 }