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    }