Don't get the FD in WindowedFile.onOpen
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / lib / WindowedFile.java
blobe9a04256a58fbf1c3b47bf4961b2a9bacc5b67cb
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;
25 import java.nio.channels.FileChannel.MapMode;
26 import java.util.zip.DataFormatException;
27 import java.util.zip.Inflater;
29 /**
30 * Read-only cached file access.
31 * <p>
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.
36 * </p>
37 * <p>
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.
42 * </p>
43 * <p>
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
48 * mapped.
49 * </p>
51 public class WindowedFile {
52 final WindowCache cache;
54 private final File fPath;
56 final int hash;
58 private RandomAccessFile fd;
60 private long length;
62 /** Total number of windows actively in the associated cache. */
63 int openCount;
65 /**
66 * Open a file for reading through window caching.
68 * @param winCache
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.
73 * @param file
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}
76 * is given.
78 public WindowedFile(final WindowCache winCache, final File file) {
79 cache = winCache;
80 fPath = file;
81 hash = System.identityHashCode(this);
82 length = Long.MAX_VALUE;
85 /**
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() {
91 return length;
94 /**
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.
105 * <p>
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.
109 * </p>
111 * @param position
112 * the starting offset, as measured in bytes from the beginning
113 * of this file, to copy from.
114 * @param dstbuf
115 * buffer to copy the bytes into.
116 * @param curs
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)
126 throws IOException {
127 return read(position, dstbuf, 0, dstbuf.length, curs);
131 * Read the requested number of bytes into a buffer.
132 * <p>
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.
136 * </p>
138 * @param position
139 * the starting offset, as measured in bytes from the beginning
140 * of this file, to copy from.
141 * @param dstbuf
142 * buffer to copy the bytes into.
143 * @param dstoff
144 * offset within <code>dstbuf</code> to start copying into.
145 * @param cnt
146 * number of bytes to copy. Must not exceed
147 * <code>dstbuf.length - dstoff</code>.
148 * @param curs
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 {
158 int remaining = cnt;
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);
162 position += r;
163 dstoff += r;
164 remaining -= r;
166 return cnt - remaining;
170 * Read the bytes into a buffer until it is full.
171 * <p>
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.
175 * </p>
177 * @param position
178 * the starting offset, as measured in bytes from the beginning
179 * of this file, to copy from.
180 * @param dstbuf
181 * buffer to copy the bytes into.
182 * @param curs
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
189 * could be read.
191 public void readFully(final long position, final byte[] dstbuf,
192 final WindowCursor curs)
193 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)
200 throws IOException, DataFormatException {
201 final Inflater inf = cache.borrowInflater();
202 try {
203 readCompressed(position, dstbuf, curs, inf);
204 } finally {
205 inf.reset();
206 cache.returnInflater(inf);
210 void readCompressed(long pos, final byte[] dstbuf, final WindowCursor curs,
211 final Inflater inf) throws IOException, DataFormatException {
212 int dstoff = 0;
213 dstoff = curs.inflate(this, (int) (pos >> cache.szb), ((int) pos)
214 & cache.szm, dstbuf, dstoff, inf);
215 pos >>= cache.szb;
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.
224 * <p>
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.
229 * </p>
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() {
241 cache.purge(this);
244 void cacheOpen() throws IOException {
245 fd = new RandomAccessFile(fPath, "r");
246 length = fd.length();
247 try {
248 onOpen();
249 } catch (IOException ioe) {
250 cacheClose();
251 throw ioe;
252 } catch (RuntimeException re) {
253 cacheClose();
254 throw re;
255 } catch (Error re) {
256 cacheClose();
257 throw re;
261 void cacheClose() {
262 try {
263 fd.close();
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
267 // not flushed, etc.
269 fd = null;
272 void loadWindow(final WindowCursor curs, final int windowId)
273 throws IOException {
274 final long position = windowId << cache.szb;
275 final int windowSize = getWindowSize(windowId);
276 if (cache.mmap) {
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);
282 curs.handle = b;
283 } else {
284 curs.window = new ByteBufferWindow(this, windowId, map);
285 curs.handle = map;
287 return;
290 final byte[] b = new byte[windowSize];
291 synchronized (fd) {
292 fd.seek(position);
293 fd.readFully(b);
295 curs.window = new ByteArrayWindow(this, windowId, b);
296 curs.handle = 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;