2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org
.spearce
.jgit
.lib
;
40 import java
.io
.EOFException
;
42 import java
.io
.IOException
;
43 import java
.io
.OutputStream
;
44 import java
.io
.RandomAccessFile
;
45 import java
.nio
.MappedByteBuffer
;
46 import java
.nio
.channels
.FileChannel
.MapMode
;
47 import java
.util
.zip
.DataFormatException
;
48 import java
.util
.zip
.Inflater
;
51 * Read-only cached file access.
53 * Supports reading data from large read-only files by loading and caching
54 * regions (windows) of the file in memory. Windows may be loaded using either
55 * traditional byte[] or by taking advantage of the operating system's virtual
56 * memory manager and mapping the file into the JVM's address space.
59 * Using no MapMode is the most portable way to access a file and does not run
60 * into problems with garbage collection, but will take longer to access as the
61 * entire window must be copied from the operating system into the Java byte[]
62 * before any single byte can be accessed.
65 * Using a specific MapMode will avoid the complete copy by mmaping in the
66 * operating system's file buffers, however this may cause problems if a large
67 * number of windows are being heavily accessed as the Java garbage collector
68 * may not be able to unmap old windows fast enough to permit new windows to be
72 public class WindowedFile
{
73 private final File fPath
;
77 private RandomAccessFile fd
;
81 /** Total number of windows actively in the associated cache. */
85 * Open a file for reading through window caching.
90 public WindowedFile(final File file
) {
92 hash
= System
.identityHashCode(this);
93 length
= Long
.MAX_VALUE
;
97 * Get the total number of bytes available in this file.
99 * @return the number of bytes contained within this file.
101 public long length() {
106 * Get the path name of this file.
108 * @return the absolute path name of the file.
110 public String
getName() {
111 return fPath
.getAbsolutePath();
115 * Read the bytes into a buffer until it is full.
117 * This routine always reads until either the requested number of bytes has
118 * been copied or EOF has been reached. Consequently callers do not need to
119 * invoke it in a loop to make sure their buffer has been fully populated.
123 * the starting offset, as measured in bytes from the beginning
124 * of this file, to copy from.
126 * buffer to copy the bytes into.
128 * current cursor for reading data from the file.
129 * @return total number of bytes read. Always <code>dstbuf.length</code>
130 * unless the requested range to copy is over the end of the file.
131 * @throws IOException
132 * a necessary window was not found in the window cache and
133 * trying to load it in from the operating system failed.
135 public int read(final long position
, final byte[] dstbuf
,
136 final WindowCursor curs
) throws IOException
{
137 return read(position
, dstbuf
, 0, dstbuf
.length
, curs
);
141 * Read the requested number of bytes into a buffer.
143 * This routine always reads until either the requested number of bytes has
144 * been copied or EOF has been reached. Consequently callers do not need to
145 * invoke it in a loop to make sure their buffer has been fully populated.
149 * the starting offset, as measured in bytes from the beginning
150 * of this file, to copy from.
152 * buffer to copy the bytes into.
154 * offset within <code>dstbuf</code> to start copying into.
156 * number of bytes to copy. Must not exceed
157 * <code>dstbuf.length - dstoff</code>.
159 * current cursor for reading data from the file.
160 * @return total number of bytes read. Always <code>length</code> unless
161 * the requested range to copy is over the end of the file.
162 * @throws IOException
163 * a necessary window was not found in the window cache and
164 * trying to load it in from the operating system failed.
166 public int read(long position
, final byte[] dstbuf
, int dstoff
,
167 final int cnt
, final WindowCursor curs
) throws IOException
{
168 return curs
.copy(this, position
, dstbuf
, dstoff
, cnt
);
172 * Read the bytes into a buffer until it is full.
174 * This routine always reads until either the requested number of bytes has
175 * been copied or EOF has been reached. Consequently callers do not need to
176 * invoke it in a loop to make sure their buffer has been fully populated.
180 * the starting offset, as measured in bytes from the beginning
181 * of this file, to copy from.
183 * buffer to copy the bytes into.
185 * current cursor for reading data from the file.
186 * @throws IOException
187 * a necessary window was not found in the window cache and
188 * trying to load it in from the operating system failed.
189 * @throws EOFException
190 * the file ended before <code>dstbuf.length</code> bytes
193 public void readFully(final long position
, final byte[] dstbuf
,
194 final WindowCursor curs
) throws IOException
{
195 if (read(position
, dstbuf
, 0, dstbuf
.length
, curs
) != dstbuf
.length
)
196 throw new EOFException();
200 * Copy the requested number of bytes to the provided output stream.
202 * This routine always reads until either the requested number of bytes has
203 * been copied or EOF has been reached.
207 * the starting offset, as measured in bytes from the beginning
208 * of this file, to copy from.
210 * temporary buffer to copy bytes into. In case of a big amount
211 * of data to copy, size of at least few kB is recommended. It
212 * does not need to be of size <code>cnt</code>, however.
214 * number of bytes to copy. Must not exceed
215 * <code>file.length - position</code>.
217 * output stream where read data is written out. No buffering is
218 * guaranteed by this method.
220 * current cursor for reading data from the file.
221 * @throws IOException
222 * a necessary window was not found in the window cache and
223 * trying to load it in from the operating system failed.
224 * @throws EOFException
225 * the file ended before <code>cnt</code> bytes could be read.
227 public void copyToStream(long position
, final byte[] buf
, long cnt
,
228 final OutputStream out
, final WindowCursor curs
)
229 throws IOException
, EOFException
{
231 int toRead
= (int) Math
.min(cnt
, buf
.length
);
232 int read
= read(position
, buf
, 0, toRead
, curs
);
234 throw new EOFException();
237 out
.write(buf
, 0, read
);
241 void readCompressed(final long position
, final byte[] dstbuf
,
242 final WindowCursor curs
) throws IOException
, DataFormatException
{
243 final Inflater inf
= InflaterCache
.get();
245 if (curs
.inflate(this, position
, dstbuf
, 0, inf
) != dstbuf
.length
)
246 throw new EOFException("Short compressed stream at " + position
);
248 InflaterCache
.release(inf
);
253 * Overridable hook called after the file is opened.
255 * This hook is invoked each time the file is opened for reading, but before
256 * the first window is mapped into the cache. Implementers are free to use
257 * any of the window access methods to obtain data, however doing so may
258 * pollute the window cache with otherwise unnecessary windows.
261 * @throws IOException
262 * something is wrong with the file, for example the caller does
263 * not understand its version header information.
265 protected void onOpen() throws IOException
{
266 // Do nothing by default.
269 /** Close this file and remove all open windows. */
270 public void close() {
271 WindowCache
.purge(this);
274 void cacheOpen() throws IOException
{
275 fd
= new RandomAccessFile(fPath
, "r");
276 length
= fd
.length();
279 } catch (IOException ioe
) {
282 } catch (RuntimeException re
) {
294 } catch (IOException err
) {
295 // Ignore a close event. We had it open only for reading.
296 // There should not be errors related to network buffers
302 void loadWindow(final WindowCursor curs
, final int windowId
,
303 final long pos
, final int windowSize
) throws IOException
{
304 if (WindowCache
.mmap
) {
305 final MappedByteBuffer map
= fd
.getChannel().map(MapMode
.READ_ONLY
,
307 if (map
.hasArray()) {
308 final byte[] b
= map
.array();
309 curs
.window
= new ByteArrayWindow(this, pos
, windowId
, b
);
312 curs
.window
= new ByteBufferWindow(this, pos
, windowId
, map
);
318 final byte[] b
= new byte[windowSize
];
323 curs
.window
= new ByteArrayWindow(this, pos
, windowId
, b
);