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
.MapMode
;
25 import java
.util
.zip
.DataFormatException
;
26 import java
.util
.zip
.Inflater
;
29 * Read-only cached file access.
31 * Supports reading data from large read-only files by loading and caching
32 * regions (windows) of the file in memory. Windows may be loaded using either
33 * traditional byte[] or by taking advantage of the operating system's virtual
34 * memory manager and mapping the file into the JVM's address space.
37 * Using no MapMode is the most portable way to access a file and does not run
38 * into problems with garbage collection, but will take longer to access as the
39 * entire window must be copied from the operating system into the Java byte[]
40 * before any single byte can be accessed.
43 * Using a specific MapMode will avoid the complete copy by mmaping in the
44 * operating system's file buffers, however this may cause problems if a large
45 * number of windows are being heavily accessed as the Java garbage collector
46 * may not be able to unmap old windows fast enough to permit new windows to be
50 public class WindowedFile
{
51 private final File fPath
;
55 private RandomAccessFile fd
;
59 /** Total number of windows actively in the associated cache. */
63 * Open a file for reading through window caching.
68 public WindowedFile(final File file
) {
70 hash
= System
.identityHashCode(this);
71 length
= Long
.MAX_VALUE
;
75 * Get the total number of bytes available in this file.
77 * @return the number of bytes contained within this file.
79 public long length() {
84 * Get the path name of this file.
86 * @return the absolute path name of the file.
88 public String
getName() {
89 return fPath
.getAbsolutePath();
93 * Read the bytes into a buffer until it is full.
95 * This routine always reads until either the requested number of bytes has
96 * been copied or EOF has been reached. Consequently callers do not need to
97 * invoke it in a loop to make sure their buffer has been fully populated.
101 * the starting offset, as measured in bytes from the beginning
102 * of this file, to copy from.
104 * buffer to copy the bytes into.
106 * current cursor for reading data from the file.
107 * @return total number of bytes read. Always <code>dstbuf.length</code>
108 * unless the requested range to copy is over the end of the file.
109 * @throws IOException
110 * a necessary window was not found in the window cache and
111 * trying to load it in from the operating system failed.
113 public int read(final long position
, final byte[] dstbuf
,
114 final WindowCursor curs
) throws IOException
{
115 return read(position
, dstbuf
, 0, dstbuf
.length
, curs
);
119 * Read the requested number of bytes into a buffer.
121 * This routine always reads until either the requested number of bytes has
122 * been copied or EOF has been reached. Consequently callers do not need to
123 * invoke it in a loop to make sure their buffer has been fully populated.
127 * the starting offset, as measured in bytes from the beginning
128 * of this file, to copy from.
130 * buffer to copy the bytes into.
132 * offset within <code>dstbuf</code> to start copying into.
134 * number of bytes to copy. Must not exceed
135 * <code>dstbuf.length - dstoff</code>.
137 * current cursor for reading data from the file.
138 * @return total number of bytes read. Always <code>length</code> unless
139 * the requested range to copy is over the end of the file.
140 * @throws IOException
141 * a necessary window was not found in the window cache and
142 * trying to load it in from the operating system failed.
144 public int read(long position
, final byte[] dstbuf
, int dstoff
,
145 final int cnt
, final WindowCursor curs
) throws IOException
{
147 while (remaining
> 0 && position
< length
) {
148 final int r
= curs
.copy(this, (int) (position
>> WindowCache
.szb
),
149 ((int) position
) & WindowCache
.szm
, dstbuf
, dstoff
,
155 return cnt
- remaining
;
159 * Read the bytes into a buffer until it is full.
161 * This routine always reads until either the requested number of bytes has
162 * been copied or EOF has been reached. Consequently callers do not need to
163 * invoke it in a loop to make sure their buffer has been fully populated.
167 * the starting offset, as measured in bytes from the beginning
168 * of this file, to copy from.
170 * buffer to copy the bytes into.
172 * current cursor for reading data from the file.
173 * @throws IOException
174 * a necessary window was not found in the window cache and
175 * trying to load it in from the operating system failed.
176 * @throws EOFException
177 * the file ended before <code>dstbuf.length</code> bytes
180 public void readFully(final long position
, final byte[] dstbuf
,
181 final WindowCursor curs
) throws IOException
{
182 if (read(position
, dstbuf
, 0, dstbuf
.length
, curs
) != dstbuf
.length
)
183 throw new EOFException();
186 void readCompressed(final long position
, final byte[] dstbuf
,
187 final WindowCursor curs
) throws IOException
, DataFormatException
{
188 final Inflater inf
= WindowCache
.borrowInflater();
190 readCompressed(position
, dstbuf
, curs
, inf
);
193 WindowCache
.returnInflater(inf
);
197 void readCompressed(long pos
, final byte[] dstbuf
, final WindowCursor curs
,
198 final Inflater inf
) throws IOException
, DataFormatException
{
200 dstoff
= curs
.inflate(this, (int) (pos
>> WindowCache
.szb
), ((int) pos
)
201 & WindowCache
.szm
, dstbuf
, dstoff
, inf
);
202 pos
>>= WindowCache
.szb
;
203 while (!inf
.finished())
204 dstoff
= curs
.inflate(this, (int) ++pos
, 0, dstbuf
, dstoff
, inf
);
205 if (dstoff
!= dstbuf
.length
)
206 throw new EOFException();
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
)
261 final long position
= windowId
<< WindowCache
.szb
;
262 final int windowSize
= getWindowSize(windowId
);
263 if (WindowCache
.mmap
) {
264 final MappedByteBuffer map
= fd
.getChannel().map(MapMode
.READ_ONLY
,
265 position
, windowSize
);
266 if (map
.hasArray()) {
267 final byte[] b
= map
.array();
268 curs
.window
= new ByteArrayWindow(this, windowId
, b
);
271 curs
.window
= new ByteBufferWindow(this, windowId
, map
);
277 final byte[] b
= new byte[windowSize
];
282 curs
.window
= new ByteArrayWindow(this, windowId
, b
);
286 int getWindowSize(final int id
) {
287 final int sz
= WindowCache
.sz
;
288 final long position
= id
<< WindowCache
.szb
;
289 return length
< position
+ sz ?
(int) (length
- position
) : sz
;