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    }