001    package com.thaiopensource.validate.auto;
002    
003    import java.io.IOException;
004    import java.io.Reader;
005    
006    /**
007     * Rewindable implementation over a reader.
008     * Modified from RewindableInputStream by replacing the input stream with a reader.
009     * @author george
010     *
011     */
012    public class RewindableReader extends Reader implements Rewindable {
013      static class Block {
014        Block next;
015        final char[] buf;
016        int used = 0;
017        static final int MIN_SIZE = 1024;
018        Block(int minSize) {
019          buf = new char[Math.max(MIN_SIZE, minSize)];
020        }
021    
022        Block() {
023          this(0);
024        }
025    
026        void append(char b) {
027          buf[used++] = b;
028        }
029    
030        void append(char[] b, int off, int len) {
031          System.arraycopy(b, off, buf, used, len);
032          used += len;
033        }
034      }
035    
036      private Block head;
037      /**
038       * If curBlockAvail > 0, then there are curBlockAvail chars available to be
039       * returned starting at curBlockPos in curBlock.buf.
040       */
041      private int curBlockAvail;
042      private Block curBlock;
043      private int curBlockPos;
044      private Block lastBlock;
045      /**
046       * true unless willNotRewind has been called
047       */
048      private boolean saving = true;
049      private final Reader in;
050      private boolean pretendClosed = false;
051      /**
052       * true if we have got an EOF from the underlying Reader
053       */
054      private boolean eof;
055    
056      public RewindableReader(Reader in) {
057        if (in == null)
058          throw new NullPointerException();
059        this.in = in;
060      }
061    
062      public void close() throws IOException {
063        if (saving) {
064          curBlockAvail = 0;
065          curBlock = null;
066          pretendClosed = true;
067        }
068        else {
069          head = null;
070          curBlock = null;
071          lastBlock = null;
072          saving = false;
073          curBlockAvail = 0;
074          in.close();
075        }
076      }
077    
078      public void rewind() {
079        if (!saving)
080          throw new IllegalStateException("rewind() after willNotRewind()");
081        pretendClosed = false;
082        if (head == null)
083          return;
084        curBlock = head;
085        curBlockPos = 0;
086        curBlockAvail = curBlock.used;
087      }
088    
089      public boolean canRewind() {
090        return saving;
091      }
092    
093      public void willNotRewind() {
094        saving = false;
095        head = null;
096        lastBlock = null;
097        if (pretendClosed) {
098          pretendClosed = false;
099          try {
100            in.close();
101          }
102          catch (IOException e) { }
103        }
104      }
105    
106      public int read() throws IOException {
107        if (curBlockAvail > 0) {
108          int c = curBlock.buf[curBlockPos++] & 0xFF;
109          --curBlockAvail;
110          if (curBlockAvail == 0) {
111            curBlock = curBlock.next;
112            if (curBlock != null) {
113              curBlockPos = 0;
114              curBlockAvail = curBlock.used;
115            }
116          }
117          return c;
118        }
119        int c = in.read();
120        if (saving && c != -1) {
121          if (lastBlock == null)
122            lastBlock = head = new Block();
123          else if (lastBlock.used == lastBlock.buf.length)
124            lastBlock = lastBlock.next = new Block();
125          lastBlock.append((char)c);
126        }
127        return c;
128      }
129    
130      public int read(char b[], int off, int len) throws IOException {
131        if (curBlockAvail == 0 && !saving)
132          return in.read(b, off, len);
133        if (b == null)
134          throw new NullPointerException();
135        if (len < 0)
136          throw new IndexOutOfBoundsException();
137        int nRead = 0;
138        if (curBlockAvail != 0) {
139          for (;;) {
140            if (len == 0)
141              return nRead;
142            b[off++] = curBlock.buf[curBlockPos++];
143            --len;
144            nRead++;
145            --curBlockAvail;
146            if (curBlockAvail == 0) {
147              curBlock = curBlock.next;
148              if (curBlock == null)
149                break;
150              curBlockAvail = curBlock.used;
151              curBlockPos = 0;
152            }
153          }
154        }
155        if (len == 0)
156          return nRead;
157        if (eof)
158          return nRead > 0 ? nRead : -1;
159        try {
160          int n = in.read(b, off, len);
161          if (n < 0) {
162            eof = true;
163            return nRead > 0 ? nRead : -1;
164          }
165          nRead += n;
166          if (saving) {
167            if (lastBlock == null)
168              lastBlock = head = new Block(n);
169            else if (lastBlock.buf.length - lastBlock.used < n) {
170              if (lastBlock.used != lastBlock.buf.length) {
171                int free = lastBlock.buf.length - lastBlock.used;
172                lastBlock.append(b, off, free);
173                off += free;
174                n -= free;
175              }
176              lastBlock = lastBlock.next = new Block(n);
177            }
178            lastBlock.append(b, off, n);
179          }
180        }
181        catch (IOException e) {
182          eof = true;
183          if (nRead == 0)
184            throw e;
185        }
186        return nRead;
187      }
188    }