001: /* BufferedSeekInputStream
002: *
003: * Created on September 14, 2006
004: *
005: * Copyright (C) 2006 Internet Archive.
006: *
007: * This file is part of the Heritrix web crawler (crawler.archive.org).
008: *
009: * Heritrix is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU Lesser Public License as published by
011: * the Free Software Foundation; either version 2.1 of the License, or
012: * any later version.
013: *
014: * Heritrix is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU Lesser Public License for more details.
018: *
019: * You should have received a copy of the GNU Lesser Public License
020: * along with Heritrix; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022: */
023: package org.archive.io;
024:
025: import java.io.IOException;
026:
027: /**
028: * Buffers data from some other SeekInputStream.
029: *
030: * @author pjack
031: */
032: public class BufferedSeekInputStream extends SeekInputStream {
033:
034: /**
035: * The underlying input stream.
036: */
037: final private SeekInputStream input;
038:
039: /**
040: * The buffered data.
041: */
042: final private byte[] buffer;
043:
044: /**
045: * The maximum offset of valid data in the buffer. Usually the same
046: * as buffer.length, but may be shorter if we're in the last region
047: * of the stream.
048: */
049: private int maxOffset;
050:
051: /**
052: * The offset of within the buffer of the next byte to read.
053: */
054: private int offset;
055:
056: /**
057: * Constructor.
058: *
059: * @param input the underlying input stream
060: * @param capacity the size of the buffer
061: * @throws IOException if an IO occurs filling the first buffer
062: */
063: public BufferedSeekInputStream(SeekInputStream input, int capacity)
064: throws IOException {
065: this .input = input;
066: this .buffer = new byte[capacity];
067: buffer();
068: }
069:
070: /**
071: * Fills the buffer.
072: *
073: * @throws IOException if an IO error occurs
074: */
075: private void buffer() throws IOException {
076: int remaining = buffer.length;
077: while (remaining > 0) {
078: int r = input.read(buffer, buffer.length - remaining,
079: remaining);
080: if (r <= 0) {
081: // Not enough information to fill the buffer
082: offset = 0;
083: maxOffset = buffer.length - remaining;
084: return;
085: }
086: remaining -= r;
087: }
088: maxOffset = buffer.length;
089: offset = 0;
090: }
091:
092: /**
093: * Ensures that the buffer is valid.
094: *
095: * @throws IOException if an IO error occurs
096: */
097: private void ensureBuffer() throws IOException {
098: if (offset >= maxOffset) {
099: buffer();
100: }
101: }
102:
103: /**
104: * Returns the number of unread bytes in the current buffer.
105: *
106: * @return the remaining bytes
107: */
108: private int remaining() {
109: return maxOffset - offset;
110: }
111:
112: @Override
113: public int read() throws IOException {
114: ensureBuffer();
115: if (maxOffset == 0) {
116: return -1;
117: }
118: int ch = buffer[offset] & 0xFF;
119: offset++;
120: return ch;
121: }
122:
123: @Override
124: public int read(byte[] buf, int ofs, int len) throws IOException {
125: ensureBuffer();
126: if (maxOffset == 0) {
127: return 0;
128: }
129: len = Math.min(len, remaining());
130: System.arraycopy(buffer, offset, buf, ofs, len);
131: offset += len;
132: return len;
133: }
134:
135: @Override
136: public int read(byte[] buf) throws IOException {
137: return read(buf, 0, buf.length);
138: }
139:
140: @Override
141: public long skip(long c) throws IOException {
142: ensureBuffer();
143: if (maxOffset == 0) {
144: return 0;
145: }
146: int count = (c > Integer.MAX_VALUE) ? Integer.MAX_VALUE
147: : (int) c;
148: int skip = Math.min(count, remaining());
149: offset += skip;
150: return skip;
151: }
152:
153: /**
154: * Returns the stream's current position.
155: *
156: * @return the current position
157: */
158: public long position() throws IOException {
159: return input.position() - buffer.length + offset;
160: }
161:
162: /**
163: * Seeks to the given position. This method avoids re-filling the buffer
164: * if at all possible.
165: *
166: * @param p the position to set
167: * @throws IOException if an IO error occurs
168: */
169: public void position(long p) throws IOException {
170: long blockStart = (input.position() - maxOffset)
171: / buffer.length * buffer.length;
172: long blockEnd = blockStart + maxOffset;
173: if ((p >= blockStart) && (p < blockEnd)) {
174: // Desired position is somewhere inside current buffer
175: long adj = p - blockStart;
176: offset = (int) adj;
177: return;
178: }
179: positionDirect(p);
180: }
181:
182: /**
183: * Positions the underlying stream at the given position, then refills
184: * the buffer.
185: *
186: * @param p the position to set
187: * @throws IOException if an IO error occurs
188: */
189: private void positionDirect(long p) throws IOException {
190: long newBlockStart = p / buffer.length * buffer.length;
191: input.position(newBlockStart);
192: buffer();
193: offset = (int) (p % buffer.length);
194: }
195:
196: /**
197: * Close the stream, including the wrapped input stream.
198: */
199: public void close() throws IOException {
200: super.close();
201: if (this.input != null) {
202: this.input.close();
203: }
204: }
205:
206: }
|