View Javadoc

1   /* BufferedSeekInputStream
2   *
3   * Created on September 14, 2006
4   *
5   * Copyright (C) 2006 Internet Archive.
6   *
7   * This file is part of the Heritrix web crawler (crawler.archive.org).
8   *
9   * Heritrix is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU Lesser Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * any later version.
13  *
14  * Heritrix is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU Lesser Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser Public License
20  * along with Heritrix; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23  package org.archive.io;
24  
25  
26  import java.io.IOException;
27  
28  
29  /***
30   * Buffers data from some other SeekInputStream.
31   * 
32   * @author pjack
33   */
34  public class BufferedSeekInputStream extends SeekInputStream {
35  
36  
37      /***
38       * The underlying input stream.
39       */
40      final private SeekInputStream input;
41  
42  
43      /***
44       * The buffered data.
45       */
46      final private byte[] buffer;
47      
48      
49      /***
50       * The maximum offset of valid data in the buffer.  Usually the same
51       * as buffer.length, but may be shorter if we're in the last region
52       * of the stream.
53       */
54      private int maxOffset;
55  
56  
57      /***
58       * The offset of within the buffer of the next byte to read.
59       */
60      private int offset;
61  
62  
63      /***
64       * Constructor.
65       * 
66       * @param input  the underlying input stream
67       * @param capacity   the size of the buffer
68       * @throws IOException   if an IO occurs filling the first buffer
69       */
70      public BufferedSeekInputStream(SeekInputStream input, int capacity) 
71      throws IOException {
72          this.input = input;
73          this.buffer = new byte[capacity];
74          buffer();
75      }
76  
77      /***
78       * Fills the buffer.
79       * 
80       * @throws IOException  if an IO error occurs
81       */
82      private void buffer() throws IOException {
83          int remaining = buffer.length;
84          while (remaining > 0) {
85              int r = input.read(buffer, buffer.length - remaining, remaining);
86              if (r <= 0) {
87                  // Not enough information to fill the buffer
88                  offset = 0;
89                  maxOffset = buffer.length - remaining;
90                  return;
91              }
92              remaining -= r;
93          }
94          maxOffset = buffer.length;
95          offset = 0;
96      }
97  
98  
99      /***
100      * Ensures that the buffer is valid.
101      * 
102      * @throws IOException  if an IO error occurs
103      */
104     private void ensureBuffer() throws IOException {
105         if (offset >= maxOffset) {
106             buffer();
107         }
108     }
109 
110 
111     /***
112      * Returns the number of unread bytes in the current buffer.
113      * 
114      * @return  the remaining bytes
115      */
116     private int remaining() {
117         return maxOffset - offset;
118     }
119 
120 
121     @Override
122     public int read() throws IOException {
123         ensureBuffer();
124         if (maxOffset == 0) {
125             return -1;
126         }
127         int ch = buffer[offset] & 0xFF;
128         offset++;
129         return ch;
130     }
131 
132 
133     @Override
134     public int read(byte[] buf, int ofs, int len) throws IOException {
135         ensureBuffer();
136         if (maxOffset == 0) {
137             return 0;
138         }
139         len = Math.min(len, remaining());
140         System.arraycopy(buffer, offset, buf, ofs, len);
141         offset += len;
142         return len;
143     }
144 
145 
146     @Override
147     public int read(byte[] buf) throws IOException {
148         return read(buf, 0, buf.length);
149     }
150 
151 
152     @Override
153     public long skip(long c) throws IOException {
154         ensureBuffer();
155         if (maxOffset == 0) {
156             return 0;
157         }
158         int count = (c > Integer.MAX_VALUE) ? Integer.MAX_VALUE : (int)c;
159         int skip = Math.min(count, remaining());
160         offset += skip;
161         return skip;
162     }
163 
164 
165     /***
166      * Returns the stream's current position.
167      * 
168      * @return  the current position
169      */
170     public long position() throws IOException {
171         return input.position() - buffer.length + offset;
172     }
173 
174 
175     /***
176      * Seeks to the given position.  This method avoids re-filling the buffer
177      * if at all possible.
178      * 
179      * @param p  the position to set
180      * @throws IOException if an IO error occurs
181      */
182     public void position(long p) throws IOException {
183         long blockStart = (input.position() - maxOffset) 
184          / buffer.length * buffer.length;
185         long blockEnd = blockStart + maxOffset;
186         if ((p >= blockStart) && (p < blockEnd)) {
187             // Desired position is somewhere inside current buffer
188             long adj = p - blockStart;
189             offset = (int)adj;
190             return;
191         }
192         positionDirect(p);
193     }
194 
195 
196     /***
197      * Positions the underlying stream at the given position, then refills
198      * the buffer.
199      * 
200      * @param p  the position to set
201      * @throws IOException  if an IO error occurs
202      */
203     private void positionDirect(long p) throws IOException {
204         long newBlockStart = p / buffer.length * buffer.length;
205         input.position(newBlockStart);
206         buffer();
207         offset = (int)(p % buffer.length);        
208     }
209 
210     /*** 
211      * Close the stream, including the wrapped input stream. 
212      */
213     public void close() throws IOException {
214         super.close();
215         if(this.input!=null) {
216             this.input.close();
217         }
218     }
219 
220 
221 }