WindowedFile fragments copying: copyToStream()
[egit/charleso.git] / org.spearce.jgit / src / org / spearce / jgit / lib / WindowedFile.java
blobf28524f177b5c465358ec1533d53ff9407b8829d
1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
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
21 * written permission.
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;
41 import java.io.File;
42 import java.io.IOException;
43 import java.io.OutputStream;
44 import java.io.RandomAccessFile;
45 import java.nio.MappedByteBuffer;
46 import java.nio.channels.FileChannel.MapMode;
47 import java.util.zip.DataFormatException;
48 import java.util.zip.Inflater;
50 /**
51 * Read-only cached file access.
52 * <p>
53 * Supports reading data from large read-only files by loading and caching
54 * regions (windows) of the file in memory. Windows may be loaded using either
55 * traditional byte[] or by taking advantage of the operating system's virtual
56 * memory manager and mapping the file into the JVM's address space.
57 * </p>
58 * <p>
59 * Using no MapMode is the most portable way to access a file and does not run
60 * into problems with garbage collection, but will take longer to access as the
61 * entire window must be copied from the operating system into the Java byte[]
62 * before any single byte can be accessed.
63 * </p>
64 * <p>
65 * Using a specific MapMode will avoid the complete copy by mmaping in the
66 * operating system's file buffers, however this may cause problems if a large
67 * number of windows are being heavily accessed as the Java garbage collector
68 * may not be able to unmap old windows fast enough to permit new windows to be
69 * mapped.
70 * </p>
72 public class WindowedFile {
73 private final File fPath;
75 final int hash;
77 private RandomAccessFile fd;
79 private long length;
81 /** Total number of windows actively in the associated cache. */
82 int openCount;
84 /**
85 * Open a file for reading through window caching.
87 * @param file
88 * the file to open.
90 public WindowedFile(final File file) {
91 fPath = file;
92 hash = System.identityHashCode(this);
93 length = Long.MAX_VALUE;
96 /**
97 * Get the total number of bytes available in this file.
99 * @return the number of bytes contained within this file.
101 public long length() {
102 return length;
106 * Get the path name of this file.
108 * @return the absolute path name of the file.
110 public String getName() {
111 return fPath.getAbsolutePath();
115 * Read the bytes into a buffer until it is full.
116 * <p>
117 * This routine always reads until either the requested number of bytes has
118 * been copied or EOF has been reached. Consequently callers do not need to
119 * invoke it in a loop to make sure their buffer has been fully populated.
120 * </p>
122 * @param position
123 * the starting offset, as measured in bytes from the beginning
124 * of this file, to copy from.
125 * @param dstbuf
126 * buffer to copy the bytes into.
127 * @param curs
128 * current cursor for reading data from the file.
129 * @return total number of bytes read. Always <code>dstbuf.length</code>
130 * unless the requested range to copy is over the end of the file.
131 * @throws IOException
132 * a necessary window was not found in the window cache and
133 * trying to load it in from the operating system failed.
135 public int read(final long position, final byte[] dstbuf,
136 final WindowCursor curs) throws IOException {
137 return read(position, dstbuf, 0, dstbuf.length, curs);
141 * Read the requested number of bytes into a buffer.
142 * <p>
143 * This routine always reads until either the requested number of bytes has
144 * been copied or EOF has been reached. Consequently callers do not need to
145 * invoke it in a loop to make sure their buffer has been fully populated.
146 * </p>
148 * @param position
149 * the starting offset, as measured in bytes from the beginning
150 * of this file, to copy from.
151 * @param dstbuf
152 * buffer to copy the bytes into.
153 * @param dstoff
154 * offset within <code>dstbuf</code> to start copying into.
155 * @param cnt
156 * number of bytes to copy. Must not exceed
157 * <code>dstbuf.length - dstoff</code>.
158 * @param curs
159 * current cursor for reading data from the file.
160 * @return total number of bytes read. Always <code>length</code> unless
161 * the requested range to copy is over the end of the file.
162 * @throws IOException
163 * a necessary window was not found in the window cache and
164 * trying to load it in from the operating system failed.
166 public int read(long position, final byte[] dstbuf, int dstoff,
167 final int cnt, final WindowCursor curs) throws IOException {
168 return curs.copy(this, position, dstbuf, dstoff, cnt);
172 * Read the bytes into a buffer until it is full.
173 * <p>
174 * This routine always reads until either the requested number of bytes has
175 * been copied or EOF has been reached. Consequently callers do not need to
176 * invoke it in a loop to make sure their buffer has been fully populated.
177 * </p>
179 * @param position
180 * the starting offset, as measured in bytes from the beginning
181 * of this file, to copy from.
182 * @param dstbuf
183 * buffer to copy the bytes into.
184 * @param curs
185 * current cursor for reading data from the file.
186 * @throws IOException
187 * a necessary window was not found in the window cache and
188 * trying to load it in from the operating system failed.
189 * @throws EOFException
190 * the file ended before <code>dstbuf.length</code> bytes
191 * could be read.
193 public void readFully(final long position, final byte[] dstbuf,
194 final WindowCursor curs) throws IOException {
195 if (read(position, dstbuf, 0, dstbuf.length, curs) != dstbuf.length)
196 throw new EOFException();
200 * Copy the requested number of bytes to the provided output stream.
201 * <p>
202 * This routine always reads until either the requested number of bytes has
203 * been copied or EOF has been reached.
204 * </p>
206 * @param position
207 * the starting offset, as measured in bytes from the beginning
208 * of this file, to copy from.
209 * @param buf
210 * temporary buffer to copy bytes into. In case of a big amount
211 * of data to copy, size of at least few kB is recommended. It
212 * does not need to be of size <code>cnt</code>, however.
213 * @param cnt
214 * number of bytes to copy. Must not exceed
215 * <code>file.length - position</code>.
216 * @param out
217 * output stream where read data is written out. No buffering is
218 * guaranteed by this method.
219 * @param curs
220 * current cursor for reading data from the file.
221 * @throws IOException
222 * a necessary window was not found in the window cache and
223 * trying to load it in from the operating system failed.
224 * @throws EOFException
225 * the file ended before <code>cnt</code> bytes could be read.
227 public void copyToStream(long position, final byte[] buf, long cnt,
228 final OutputStream out, final WindowCursor curs)
229 throws IOException, EOFException {
230 while (cnt > 0) {
231 int toRead = (int) Math.min(cnt, buf.length);
232 int read = read(position, buf, 0, toRead, curs);
233 if (read != toRead)
234 throw new EOFException();
235 position += read;
236 cnt -= read;
237 out.write(buf, 0, read);
241 void readCompressed(final long position, final byte[] dstbuf,
242 final WindowCursor curs) throws IOException, DataFormatException {
243 final Inflater inf = InflaterCache.get();
244 try {
245 if (curs.inflate(this, position, dstbuf, 0, inf) != dstbuf.length)
246 throw new EOFException("Short compressed stream at " + position);
247 } finally {
248 InflaterCache.release(inf);
253 * Overridable hook called after the file is opened.
254 * <p>
255 * This hook is invoked each time the file is opened for reading, but before
256 * the first window is mapped into the cache. Implementers are free to use
257 * any of the window access methods to obtain data, however doing so may
258 * pollute the window cache with otherwise unnecessary windows.
259 * </p>
261 * @throws IOException
262 * something is wrong with the file, for example the caller does
263 * not understand its version header information.
265 protected void onOpen() throws IOException {
266 // Do nothing by default.
269 /** Close this file and remove all open windows. */
270 public void close() {
271 WindowCache.purge(this);
274 void cacheOpen() throws IOException {
275 fd = new RandomAccessFile(fPath, "r");
276 length = fd.length();
277 try {
278 onOpen();
279 } catch (IOException ioe) {
280 cacheClose();
281 throw ioe;
282 } catch (RuntimeException re) {
283 cacheClose();
284 throw re;
285 } catch (Error re) {
286 cacheClose();
287 throw re;
291 void cacheClose() {
292 try {
293 fd.close();
294 } catch (IOException err) {
295 // Ignore a close event. We had it open only for reading.
296 // There should not be errors related to network buffers
297 // not flushed, etc.
299 fd = null;
302 void loadWindow(final WindowCursor curs, final int windowId,
303 final long pos, final int windowSize) throws IOException {
304 if (WindowCache.mmap) {
305 final MappedByteBuffer map = fd.getChannel().map(MapMode.READ_ONLY,
306 pos, windowSize);
307 if (map.hasArray()) {
308 final byte[] b = map.array();
309 curs.window = new ByteArrayWindow(this, pos, windowId, b);
310 curs.handle = b;
311 } else {
312 curs.window = new ByteBufferWindow(this, pos, windowId, map);
313 curs.handle = map;
315 return;
318 final byte[] b = new byte[windowSize];
319 synchronized (fd) {
320 fd.seek(pos);
321 fd.readFully(b);
323 curs.window = new ByteArrayWindow(this, pos, windowId, b);
324 curs.handle = b;