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 }