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
.RandomAccessFile
;
44 import java
.nio
.MappedByteBuffer
;
45 import java
.nio
.channels
.FileChannel
.MapMode
;
46 import java
.util
.zip
.DataFormatException
;
47 import java
.util
.zip
.Inflater
;
50 * Read-only cached file access.
52 * Supports reading data from large read-only files by loading and caching
53 * regions (windows) of the file in memory. Windows may be loaded using either
54 * traditional byte[] or by taking advantage of the operating system's virtual
55 * memory manager and mapping the file into the JVM's address space.
58 * Using no MapMode is the most portable way to access a file and does not run
59 * into problems with garbage collection, but will take longer to access as the
60 * entire window must be copied from the operating system into the Java byte[]
61 * before any single byte can be accessed.
64 * Using a specific MapMode will avoid the complete copy by mmaping in the
65 * operating system's file buffers, however this may cause problems if a large
66 * number of windows are being heavily accessed as the Java garbage collector
67 * may not be able to unmap old windows fast enough to permit new windows to be
71 public class WindowedFile
{
72 private final File fPath
;
76 private RandomAccessFile fd
;
80 /** Total number of windows actively in the associated cache. */
84 * Open a file for reading through window caching.
89 public WindowedFile(final File file
) {
91 hash
= System
.identityHashCode(this);
92 length
= Long
.MAX_VALUE
;
96 * Get the total number of bytes available in this file.
98 * @return the number of bytes contained within this file.
100 public long length() {
105 * Get the path name of this file.
107 * @return the absolute path name of the file.
109 public String
getName() {
110 return fPath
.getAbsolutePath();
114 * Read the bytes into a buffer until it is full.
116 * This routine always reads until either the requested number of bytes has
117 * been copied or EOF has been reached. Consequently callers do not need to
118 * invoke it in a loop to make sure their buffer has been fully populated.
122 * the starting offset, as measured in bytes from the beginning
123 * of this file, to copy from.
125 * buffer to copy the bytes into.
127 * current cursor for reading data from the file.
128 * @return total number of bytes read. Always <code>dstbuf.length</code>
129 * unless the requested range to copy is over the end of the file.
130 * @throws IOException
131 * a necessary window was not found in the window cache and
132 * trying to load it in from the operating system failed.
134 public int read(final long position
, final byte[] dstbuf
,
135 final WindowCursor curs
) throws IOException
{
136 return read(position
, dstbuf
, 0, dstbuf
.length
, curs
);
140 * Read the requested number of bytes into a buffer.
142 * This routine always reads until either the requested number of bytes has
143 * been copied or EOF has been reached. Consequently callers do not need to
144 * invoke it in a loop to make sure their buffer has been fully populated.
148 * the starting offset, as measured in bytes from the beginning
149 * of this file, to copy from.
151 * buffer to copy the bytes into.
153 * offset within <code>dstbuf</code> to start copying into.
155 * number of bytes to copy. Must not exceed
156 * <code>dstbuf.length - dstoff</code>.
158 * current cursor for reading data from the file.
159 * @return total number of bytes read. Always <code>length</code> unless
160 * the requested range to copy is over the end of the file.
161 * @throws IOException
162 * a necessary window was not found in the window cache and
163 * trying to load it in from the operating system failed.
165 public int read(long position
, final byte[] dstbuf
, int dstoff
,
166 final int cnt
, final WindowCursor curs
) throws IOException
{
167 return curs
.copy(this, position
, dstbuf
, dstoff
, cnt
);
171 * Read the bytes into a buffer until it is full.
173 * This routine always reads until either the requested number of bytes has
174 * been copied or EOF has been reached. Consequently callers do not need to
175 * invoke it in a loop to make sure their buffer has been fully populated.
179 * the starting offset, as measured in bytes from the beginning
180 * of this file, to copy from.
182 * buffer to copy the bytes into.
184 * current cursor for reading data from the file.
185 * @throws IOException
186 * a necessary window was not found in the window cache and
187 * trying to load it in from the operating system failed.
188 * @throws EOFException
189 * the file ended before <code>dstbuf.length</code> bytes
192 public void readFully(final long position
, final byte[] dstbuf
,
193 final WindowCursor curs
) throws IOException
{
194 if (read(position
, dstbuf
, 0, dstbuf
.length
, curs
) != dstbuf
.length
)
195 throw new EOFException();
198 void readCompressed(final long position
, final byte[] dstbuf
,
199 final WindowCursor curs
) throws IOException
, DataFormatException
{
200 final Inflater inf
= InflaterCache
.get();
202 if (curs
.inflate(this, position
, dstbuf
, 0, inf
) != dstbuf
.length
)
203 throw new EOFException("Short compressed stream at " + position
);
205 InflaterCache
.release(inf
);
210 * Overridable hook called after the file is opened.
212 * This hook is invoked each time the file is opened for reading, but before
213 * the first window is mapped into the cache. Implementers are free to use
214 * any of the window access methods to obtain data, however doing so may
215 * pollute the window cache with otherwise unnecessary windows.
218 * @throws IOException
219 * something is wrong with the file, for example the caller does
220 * not understand its version header information.
222 protected void onOpen() throws IOException
{
223 // Do nothing by default.
226 /** Close this file and remove all open windows. */
227 public void close() {
228 WindowCache
.purge(this);
231 void cacheOpen() throws IOException
{
232 fd
= new RandomAccessFile(fPath
, "r");
233 length
= fd
.length();
236 } catch (IOException ioe
) {
239 } catch (RuntimeException re
) {
251 } catch (IOException err
) {
252 // Ignore a close event. We had it open only for reading.
253 // There should not be errors related to network buffers
259 void loadWindow(final WindowCursor curs
, final int windowId
,
260 final long pos
, final int windowSize
) throws IOException
{
261 if (WindowCache
.mmap
) {
262 final MappedByteBuffer map
= fd
.getChannel().map(MapMode
.READ_ONLY
,
264 if (map
.hasArray()) {
265 final byte[] b
= map
.array();
266 curs
.window
= new ByteArrayWindow(this, pos
, windowId
, b
);
269 curs
.window
= new ByteBufferWindow(this, pos
, windowId
, map
);
275 final byte[] b
= new byte[windowSize
];
280 curs
.window
= new ByteArrayWindow(this, pos
, windowId
, b
);