001 /*
002 * This file is part of muCommander, http://www.mucommander.com
003 * Copyright (C) 2002-2008 Maxence Bernard
004 *
005 * muCommander is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * muCommander is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018
019 package com.mucommander.io;
020
021 import java.io.IOException;
022
023 /**
024 * <code>BlockRandomInputStream</code> is a specialized-yet-still-abstract <code>RandomAccessInputStream</code> that
025 * is geared towards resources that are read block by block, either because of a particular constrain or for performance
026 * reasons. This class typically comes in handy for network resources such as HTTP which have to request a block range
027 * for reading the resource.
028 *
029 * <p>Seeking inside the file is implemented transparently by reading a block starting at the seek offset.
030 * If {@link #seek(long)} is called with an offset that is within the current block, no read occurs.
031 * The block size should be carefully chosen as it affects seek performance and thus overall performance greatly:
032 * the larger the block size, the more data is fetched when seeking outside the current block and consequently the
033 * longer it takes to reposition the stream. On the other hand, a larger block size will yield better performance when
034 * reading the resource sequentially, as it lessens the overhead of requesting a particular block.</p>
035 *
036 * @author Maxence Bernard
037 */
038 public abstract class BlockRandomInputStream extends RandomAccessInputStream {
039
040 /** Block size, i.e. length of the {@link #block} array */
041 protected final int blockSize;
042
043 /** Contains the current file block. Data may end before the array does. */
044 private final byte block[];
045
046 /** Current offset within the block array to the next byte to return */
047 private int blockOff;
048
049 /** Length of the current block */
050 private int blockLen;
051
052 /** Global offset within the file */
053 private long offset;
054
055
056 /**
057 * Creates a new <code>BlockRandomInputStream</code> using the specified block size.
058 *
059 * <p>The block size should be carefully chosen as it affects seek performance and thus overall performance greatly:
060 * the larger the block size, the more data is fetched when seeking outside the current block and consequently the
061 * longer it takes to reposition the stream. On the other hand, a larger block size will yield better performance
062 * when reading the resource sequentially, as it lessens the overhead of requesting a particular block.</p>
063 *
064 * @param blockSize controls the amount of data requested when reading a block
065 */
066 protected BlockRandomInputStream(int blockSize) {
067 this.blockSize = blockSize;
068 block = new byte[blockSize];
069 }
070
071 /**
072 * Returns <code>true</code> if the end of file has been reached.
073 *
074 * @return true if the end of file has been reached.
075 * @throws IOException if an I/O error occurred
076 */
077 private boolean eofReached() throws IOException {
078 return offset>=getLength();
079 }
080
081 /**
082 * Checks if the current buffered block has been read completely (i.e. no more data is available) and if it has,
083 * calls {@link #readBlock(long, byte[], int)} to fetch the next block.
084 *
085 * @throws IOException if an I/O error occurred
086 */
087 private void checkBuffer() throws IOException {
088 if(blockOff >= blockLen) // True initially
089 readBlock();
090 }
091
092 /**
093 * Calls {@link #readBlock(long, byte[], int)} to read a block of up to <code>blockSize</code>, less if the
094 * the end of file is near.
095 *
096 * @throws IOException if an I/O error occurred
097 */
098 private void readBlock() throws IOException {
099 int len = Math.min((int)(getLength()-offset), blockSize);
100 // update len with the number of bytes actually read
101 len = readBlock(offset, block, len);
102
103 // Note: these fields won't be updated if an I/O error occurs
104 this.blockOff = 0;
105 this.blockLen = len;
106 }
107
108
109 ////////////////////////////////////////////
110 // RandomAccessInputStream implementation //
111 ////////////////////////////////////////////
112
113 public int read() throws IOException {
114 if(eofReached())
115 return -1;
116
117 checkBuffer();
118
119 int ret = block[blockOff];
120
121 blockOff++;
122 offset ++;
123
124 return ret;
125 }
126
127 public int read(byte b[], int off, int len) throws IOException {
128 if(len==0)
129 return 0;
130
131 if(eofReached())
132 return -1;
133
134 checkBuffer();
135
136 int nbBytes = Math.min(len, blockLen - blockOff);
137 System.arraycopy(block, blockOff, b, off, nbBytes);
138
139 blockOff += nbBytes;
140 offset += nbBytes;
141
142 return nbBytes;
143 }
144
145 public long getOffset() throws IOException {
146 return offset;
147 }
148
149 public void seek(long newOffset) throws IOException {
150 // If the new offset is within the current buffer's range, simply reposition the offsets
151 if(newOffset>=offset && newOffset<offset+ blockLen) {
152 blockOff += (int)(newOffset-offset);
153 offset = newOffset;
154 }
155 // If not, retrieve a block of data starting at the new offset and fill the buffer with it
156 else {
157 offset = newOffset;
158 readBlock();
159 }
160 }
161
162
163 ///////////////////////
164 // Abstract methods //
165 ///////////////////////
166
167 /**
168 * Reads a block, that spawns from <code>fileOffset</code> to <code>fileOffset+blockLen</code>, an returns
169 * the number of bytes that could be read, normally <code>blockLen</code> but can be less.
170 *
171 * <p>Note that <code>blockLen</code> may be smaller than {@link #blockSize} if the end of file is near, to prevent
172 * <code>EOF</code> from being reached. In other words, <code>fileOffset+blockLen</code> should theorically not
173 * exceed the file's length, but this could happen in the unlikely event that the file just shrinked after
174 * {@link #getLength()} was last called. So this method's implementation should handle the case where
175 * <code>EOF</code> is reached prematurely and return the number of bytes that were actually read.</p>
176 *
177 * @param fileOffset global file offset that marks the beginning of the block
178 * @param block the array to fill with data, starting at 0
179 * @param blockLen number of bytes to read
180 * @return the number of bytes that were actually read, normally blockLen unless
181 * @throws IOException if an I/O error occurred
182 */
183 protected abstract int readBlock(long fileOffset, byte block[], int blockLen) throws IOException;
184 }