    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 }