001 /*
002 * Copyright (c) 2001-2003 Thai Open Source Software Center Ltd
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions
007 * are met:
008 *
009 * * Redistributions of source code must retain the above copyright
010 * notice, this list of conditions and the following disclaimer.
011 * * Redistributions in binary form must reproduce the above
012 * copyright notice, this list of conditions and the following
013 * disclaimer in the documentation and/or other materials provided
014 * with the distribution.
015 * * Neither the name of the Thai Open Source Software Center Ltd nor
016 * the names of its contributors may be used to endorse or promote
017 * products derived from this software without specific prior
018 * written permission.
019 *
020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
023 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
024 * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
025 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
026 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
027 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
028 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
029 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
030 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
031 * POSSIBILITY OF SUCH DAMAGE.
032 */
033
034 package nu.validator.htmlparser.rewindable;
035
036 import java.io.IOException;
037 import java.io.InputStream;
038
039 public class RewindableInputStream extends InputStream implements Rewindable {
040 static class Block {
041 Block next;
042
043 final byte[] buf;
044
045 int used = 0;
046
047 static final int MIN_SIZE = 1024;
048
049 Block(int minSize) {
050 buf = new byte[Math.max(MIN_SIZE, minSize)];
051 }
052
053 Block() {
054 this(0);
055 }
056
057 void append(byte b) {
058 buf[used++] = b;
059 }
060
061 void append(byte[] b, int off, int len) {
062 System.arraycopy(b, off, buf, used, len);
063 used += len;
064 }
065 }
066
067 private Block head;
068
069 /**
070 * If curBlockAvail > 0, then there are curBlockAvail bytes available to be
071 * returned starting at curBlockPos in curBlock.buf.
072 */
073 private int curBlockAvail;
074
075 private Block curBlock;
076
077 private int curBlockPos;
078
079 private Block lastBlock;
080
081 /**
082 * true unless willNotRewind has been called
083 */
084 private boolean saving = true;
085
086 private final InputStream in;
087
088 private boolean pretendClosed = false;
089
090 /**
091 * true if we have got an EOF from the underlying InputStream
092 */
093 private boolean eof;
094
095 public RewindableInputStream(InputStream in) {
096 if (in == null)
097 throw new NullPointerException();
098 this.in = in;
099 }
100
101 public void close() throws IOException {
102 if (saving) {
103 curBlockAvail = 0;
104 curBlock = null;
105 pretendClosed = true;
106 } else {
107 head = null;
108 curBlock = null;
109 lastBlock = null;
110 saving = false;
111 curBlockAvail = 0;
112 in.close();
113 }
114 }
115
116 public void rewind() {
117 if (!saving)
118 throw new IllegalStateException("rewind() after willNotRewind()");
119 pretendClosed = false;
120 if (head == null)
121 return;
122 curBlock = head;
123 curBlockPos = 0;
124 curBlockAvail = curBlock.used;
125 }
126
127 public boolean canRewind() {
128 return saving;
129 }
130
131 public void willNotRewind() {
132 saving = false;
133 head = null;
134 lastBlock = null;
135 if (pretendClosed) {
136 pretendClosed = false;
137 try {
138 in.close();
139 } catch (IOException e) {
140 }
141 }
142 }
143
144 public int read() throws IOException {
145 if (curBlockAvail > 0) {
146 int c = curBlock.buf[curBlockPos++] & 0xFF;
147 --curBlockAvail;
148 if (curBlockAvail == 0) {
149 curBlock = curBlock.next;
150 if (curBlock != null) {
151 curBlockPos = 0;
152 curBlockAvail = curBlock.used;
153 }
154 }
155 return c;
156 }
157 int c = in.read();
158 if (saving && c != -1) {
159 if (lastBlock == null)
160 lastBlock = head = new Block();
161 else if (lastBlock.used == lastBlock.buf.length)
162 lastBlock = lastBlock.next = new Block();
163 lastBlock.append((byte) c);
164 }
165 return c;
166 }
167
168 public int read(byte b[], int off, int len) throws IOException {
169 if (curBlockAvail == 0 && !saving)
170 return in.read(b, off, len);
171 if (b == null)
172 throw new NullPointerException();
173 if (len < 0)
174 throw new IndexOutOfBoundsException();
175 int nRead = 0;
176 if (curBlockAvail != 0) {
177 for (;;) {
178 if (len == 0)
179 return nRead;
180 b[off++] = curBlock.buf[curBlockPos++];
181 --len;
182 nRead++;
183 --curBlockAvail;
184 if (curBlockAvail == 0) {
185 curBlock = curBlock.next;
186 if (curBlock == null)
187 break;
188 curBlockAvail = curBlock.used;
189 curBlockPos = 0;
190 }
191 }
192 }
193 if (len == 0)
194 return nRead;
195 if (eof)
196 return nRead > 0 ? nRead : -1;
197 try {
198 int n = in.read(b, off, len);
199 if (n < 0) {
200 eof = true;
201 return nRead > 0 ? nRead : -1;
202 }
203 nRead += n;
204 if (saving) {
205 if (lastBlock == null)
206 lastBlock = head = new Block(n);
207 else if (lastBlock.buf.length - lastBlock.used < n) {
208 if (lastBlock.used != lastBlock.buf.length) {
209 int free = lastBlock.buf.length - lastBlock.used;
210 lastBlock.append(b, off, free);
211 off += free;
212 n -= free;
213 }
214 lastBlock = lastBlock.next = new Block(n);
215 }
216 lastBlock.append(b, off, n);
217 }
218 } catch (IOException e) {
219 eof = true;
220 if (nRead == 0)
221 throw e;
222 }
223 return nRead;
224 }
225
226 public int available() throws IOException {
227 if (curBlockAvail == 0)
228 return in.available();
229 int n = curBlockAvail;
230 for (Block b = curBlock.next; b != null; b = b.next)
231 n += b.used;
232 return n + in.available();
233 }
234
235 }