2 * Copyright (C) 2006 Shawn Pearce <spearce@spearce.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License, version 2, as published by the Free Software Foundation.
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this library; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
17 package org
.spearce
.jgit
.lib
;
19 import java
.io
.EOFException
;
21 import java
.io
.IOException
;
22 import java
.io
.RandomAccessFile
;
23 import java
.nio
.MappedByteBuffer
;
24 import java
.nio
.channels
.FileChannel
;
25 import java
.nio
.channels
.FileChannel
.MapMode
;
26 import java
.util
.zip
.DataFormatException
;
27 import java
.util
.zip
.Inflater
;
30 * Read-only cached file access.
32 * Supports reading data from large read-only files by loading and caching
33 * regions (windows) of the file in memory. Windows may be loaded using either
34 * traditional byte[] or by taking advantage of the operating system's virtual
35 * memory manager and mapping the file into the JVM's address space.
38 * Using no MapMode is the most portable way to access a file and does not run
39 * into problems with garbage collection, but will take longer to access as the
40 * entire window must be copied from the operating system into the Java byte[]
41 * before any single byte can be accessed.
44 * Using a specific MapMode will avoid the complete copy by mmaping in the
45 * operating system's file buffers, however this may cause problems if a large
46 * number of windows are being heavily accessed as the Java garbage collector
47 * may not be able to unmap old windows fast enough to permit new windows to be
51 public class WindowedFile
{
52 final WindowCache cache
;
54 private final File fPath
;
58 private RandomAccessFile fd
;
62 /** Total number of windows actively in the associated cache. */
66 * Open a file for reading through window caching.
69 * the cache this file will maintain its windows in. All windows
70 * in the same cache will be considered for cache eviction, so
71 * multiple files from the same Git repository probably should
72 * use the same window cache.
74 * the file to open. The file will be opened for reading only,
75 * unless {@link FileChannel.MapMode#READ_WRITE} or {@link FileChannel.MapMode#PRIVATE}
78 public WindowedFile(final WindowCache winCache
, final File file
) {
81 hash
= System
.identityHashCode(this);
82 length
= Long
.MAX_VALUE
;
86 * Get the total number of bytes available in this file.
88 * @return the number of bytes contained within this file.
90 public long length() {
95 * Get the path name of this file.
97 * @return the absolute path name of the file.
99 public String
getName() {
100 return fPath
.getAbsolutePath();
104 * Read the bytes into a buffer until it is full.
106 * This routine always reads until either the requested number of bytes has
107 * been copied or EOF has been reached. Consequently callers do not need to
108 * invoke it in a loop to make sure their buffer has been fully populated.
112 * the starting offset, as measured in bytes from the beginning
113 * of this file, to copy from.
115 * buffer to copy the bytes into.
117 * current cursor for reading data from the file.
118 * @return total number of bytes read. Always <code>dstbuf.length</code>
119 * unless the requested range to copy is over the end of the file.
120 * @throws IOException
121 * a necessary window was not found in the window cache and
122 * trying to load it in from the operating system failed.
124 public int read(final long position
, final byte[] dstbuf
,
125 final WindowCursor curs
)
127 return read(position
, dstbuf
, 0, dstbuf
.length
, curs
);
131 * Read the requested number of bytes into a buffer.
133 * This routine always reads until either the requested number of bytes has
134 * been copied or EOF has been reached. Consequently callers do not need to
135 * invoke it in a loop to make sure their buffer has been fully populated.
139 * the starting offset, as measured in bytes from the beginning
140 * of this file, to copy from.
142 * buffer to copy the bytes into.
144 * offset within <code>dstbuf</code> to start copying into.
146 * number of bytes to copy. Must not exceed
147 * <code>dstbuf.length - dstoff</code>.
149 * current cursor for reading data from the file.
150 * @return total number of bytes read. Always <code>length</code> unless
151 * the requested range to copy is over the end of the file.
152 * @throws IOException
153 * a necessary window was not found in the window cache and
154 * trying to load it in from the operating system failed.
156 public int read(long position
, final byte[] dstbuf
, int dstoff
,
157 final int cnt
, final WindowCursor curs
) throws IOException
{
159 while (remaining
> 0 && position
< length
) {
160 final int r
= curs
.copy(this, (int) (position
>> cache
.szb
),
161 ((int) position
) & cache
.szm
, dstbuf
, dstoff
, remaining
);
166 return cnt
- remaining
;
170 * Read the bytes into a buffer until it is full.
172 * This routine always reads until either the requested number of bytes has
173 * been copied or EOF has been reached. Consequently callers do not need to
174 * invoke it in a loop to make sure their buffer has been fully populated.
178 * the starting offset, as measured in bytes from the beginning
179 * of this file, to copy from.
181 * buffer to copy the bytes into.
183 * current cursor for reading data from the file.
184 * @throws IOException
185 * a necessary window was not found in the window cache and
186 * trying to load it in from the operating system failed.
187 * @throws EOFException
188 * the file ended before <code>dstbuf.length</code> bytes
191 public void readFully(final long position
, final byte[] dstbuf
,
192 final WindowCursor curs
)
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
)
200 throws IOException
, DataFormatException
{
201 final Inflater inf
= cache
.borrowInflater();
203 readCompressed(position
, dstbuf
, curs
, inf
);
206 cache
.returnInflater(inf
);
210 void readCompressed(long pos
, final byte[] dstbuf
, final WindowCursor curs
,
211 final Inflater inf
) throws IOException
, DataFormatException
{
213 dstoff
= curs
.inflate(this, (int) (pos
>> cache
.szb
), ((int) pos
)
214 & cache
.szm
, dstbuf
, dstoff
, inf
);
216 while (!inf
.finished())
217 dstoff
= curs
.inflate(this, (int) ++pos
, 0, dstbuf
, dstoff
, inf
);
218 if (dstoff
!= dstbuf
.length
)
219 throw new EOFException();
223 * Overridable hook called after the file is opened.
225 * This hook is invoked each time the file is opened for reading, but before
226 * the first window is mapped into the cache. Implementers are free to use
227 * any of the window access methods to obtain data, however doing so may
228 * pollute the window cache with otherwise unnecessary windows.
231 * @throws IOException
232 * something is wrong with the file, for example the caller does
233 * not understand its version header information.
235 protected void onOpen() throws IOException
{
236 // Do nothing by default.
239 /** Close this file and remove all open windows. */
240 public void close() {
244 void cacheOpen() throws IOException
{
245 fd
= new RandomAccessFile(fPath
, "r");
246 length
= fd
.length();
249 } catch (IOException ioe
) {
252 } catch (RuntimeException re
) {
264 } catch (IOException err
) {
265 // Ignore a close event. We had it open only for reading.
266 // There should not be errors related to network buffers
272 void loadWindow(final WindowCursor curs
, final int windowId
)
274 final long position
= windowId
<< cache
.szb
;
275 final int windowSize
= getWindowSize(windowId
);
277 final MappedByteBuffer map
= fd
.getChannel().map(MapMode
.READ_ONLY
,
278 position
, windowSize
);
279 if (map
.hasArray()) {
280 final byte[] b
= map
.array();
281 curs
.window
= new ByteArrayWindow(this, windowId
, b
);
284 curs
.window
= new ByteBufferWindow(this, windowId
, map
);
290 final byte[] b
= new byte[windowSize
];
295 curs
.window
= new ByteArrayWindow(this, windowId
, b
);
299 int getWindowSize(final int id
) {
300 final int sz
= cache
.sz
;
301 final long position
= id
<< cache
.szb
;
302 return length
< position
+ sz ?
(int) (length
- position
) : sz
;