Always use a single WindowCache for the entire JVM
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / WindowedFile.java
blob8ab69304bad32a18fe1823077c4ce4ec27dbbe3a
1 /*
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;
20 import java.io.File;
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;
28 /**
29 * Read-only cached file access.
30 * <p>
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.
35 * </p>
36 * <p>
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.
41 * </p>
42 * <p>
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
47 * mapped.
48 * </p>
50 public class WindowedFile {
51 private final File fPath;
53 final int hash;
55 private RandomAccessFile fd;
57 private long length;
59 /** Total number of windows actively in the associated cache. */
60 int openCount;
62 /**
63 * Open a file for reading through window caching.
65 * @param file
66 * the file to open.
68 public WindowedFile(final File file) {
69 fPath = file;
70 hash = System.identityHashCode(this);
71 length = Long.MAX_VALUE;
74 /**
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() {
80 return length;
83 /**
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();
92 /**
93 * Read the bytes into a buffer until it is full.
94 * <p>
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.
98 * </p>
100 * @param position
101 * the starting offset, as measured in bytes from the beginning
102 * of this file, to copy from.
103 * @param dstbuf
104 * buffer to copy the bytes into.
105 * @param curs
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.
120 * <p>
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.
124 * </p>
126 * @param position
127 * the starting offset, as measured in bytes from the beginning
128 * of this file, to copy from.
129 * @param dstbuf
130 * buffer to copy the bytes into.
131 * @param dstoff
132 * offset within <code>dstbuf</code> to start copying into.
133 * @param cnt
134 * number of bytes to copy. Must not exceed
135 * <code>dstbuf.length - dstoff</code>.
136 * @param curs
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 {
146 int remaining = cnt;
147 while (remaining > 0 && position < length) {
148 final int r = curs.copy(this, (int) (position >> WindowCache.szb),
149 ((int) position) & WindowCache.szm, dstbuf, dstoff,
150 remaining);
151 position += r;
152 dstoff += r;
153 remaining -= r;
155 return cnt - remaining;
159 * Read the bytes into a buffer until it is full.
160 * <p>
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.
164 * </p>
166 * @param position
167 * the starting offset, as measured in bytes from the beginning
168 * of this file, to copy from.
169 * @param dstbuf
170 * buffer to copy the bytes into.
171 * @param curs
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
178 * could be read.
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();
189 try {
190 readCompressed(position, dstbuf, curs, inf);
191 } finally {
192 inf.reset();
193 WindowCache.returnInflater(inf);
197 void readCompressed(long pos, final byte[] dstbuf, final WindowCursor curs,
198 final Inflater inf) throws IOException, DataFormatException {
199 int dstoff = 0;
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.
211 * <p>
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.
216 * </p>
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();
234 try {
235 onOpen();
236 } catch (IOException ioe) {
237 cacheClose();
238 throw ioe;
239 } catch (RuntimeException re) {
240 cacheClose();
241 throw re;
242 } catch (Error re) {
243 cacheClose();
244 throw re;
248 void cacheClose() {
249 try {
250 fd.close();
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
254 // not flushed, etc.
256 fd = null;
259 void loadWindow(final WindowCursor curs, final int windowId)
260 throws IOException {
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);
269 curs.handle = b;
270 } else {
271 curs.window = new ByteBufferWindow(this, windowId, map);
272 curs.handle = map;
274 return;
277 final byte[] b = new byte[windowSize];
278 synchronized (fd) {
279 fd.seek(position);
280 fd.readFully(b);
282 curs.window = new ByteArrayWindow(this, windowId, b);
283 curs.handle = 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;