Reduce the number of temporary byte[20] arrays allocated.
[egit/egit-new.git] / org.spearce.jgit / src / org / spearce / jgit / lib / PackFile.java
blobfacec5ac95299a618450879db80230e523ff7a2c
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 Lesser General Public
6 * License, version 2.1, 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 * Lesser General Public License for more details.
13 * You should have received a copy of the GNU Lesser 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.File;
20 import java.io.IOException;
21 import java.util.zip.DataFormatException;
23 import org.spearce.jgit.errors.CorruptObjectException;
25 public class PackFile {
26 private static final int IDX_HDR_LEN = 256 * 4;
28 private static final byte[] SIGNATURE = { 'P', 'A', 'C', 'K' };
30 private final Repository repo;
32 private final WindowedFile pack;
34 private final WindowedFile idx;
36 private final long[] idxHeader;
38 private long objectCnt;
40 public PackFile(final Repository parentRepo, final File packFile)
41 throws IOException {
42 repo = parentRepo;
43 // FIXME window size and mmap type should be configurable
44 pack = new WindowedFile(repo.getWindowCache(), packFile,
45 64 * 1024 * 1024, false);
46 try {
47 readPackHeader();
49 final String name = packFile.getName();
50 final int dot = name.lastIndexOf('.');
51 final File idxFile = new File(packFile.getParentFile(), name
52 .substring(0, dot)
53 + ".idx");
54 // FIXME window size and mmap type should be configurable
55 idx = new WindowedFile(repo.getWindowCache(), idxFile,
56 64 * 1024 * 1024, false);
57 try {
58 idxHeader = readIndexHeader();
59 } catch (IOException ioe) {
60 try {
61 idx.close();
62 } catch (IOException err2) {
64 throw ioe;
66 } catch (IOException ioe) {
67 try {
68 pack.close();
69 } catch (IOException err2) {
71 throw ioe;
75 ObjectLoader resolveBase(final long ofs) throws IOException {
76 return reader(ofs, new byte[Constants.OBJECT_ID_LENGTH]);
79 /**
80 * Determine if an object is contained within the pack file.
81 * <p>
82 * For performance reasons only the index file is searched; the main
83 * pack content is ignored entirely.
84 * </p>
86 * @param id
87 * the object to look for. Must not be null.
88 * @param tmp
89 * a temporary buffer loaned to this pack for use during
90 * the search. This buffer must be at least
91 * {@link Constants#OBJECT_ID_LENGTH} bytes in size. The
92 * buffer will be overwritten during the search, but is
93 * unused upon return.
94 * @return true if the object is in this pack; false otherwise.
95 * @throws IOException
96 * there was an error reading data from the pack's index
97 * file.
99 public boolean hasObject(final ObjectId id, final byte[] tmp)
100 throws IOException {
101 return findOffset(id, tmp) != -1;
105 * Get an object from this pack.
106 * <p>
107 * For performance reasons the caller is responsible for supplying a
108 * temporary buffer of at least {@link Constants#OBJECT_ID_LENGTH} bytes
109 * for use during searching. If an object loader is returned this
110 * temporary buffer becomes the property of the object loader and must
111 * not be overwritten by the caller. If no object loader is returned
112 * then the temporary buffer remains the property of the caller and may
113 * be given to a different pack file to continue searching for the
114 * needed object.
115 * </p>
117 * @param id
118 * the object to obtain from the pack. Must not be null.
119 * @param tmp
120 * a temporary buffer loaned to this pack for use during
121 * the search, and given to the returned loader if the
122 * object is found. This buffer must be at least
123 * {@link Constants#OBJECT_ID_LENGTH} bytes in size. The
124 * buffer will be overwritten during the search. The
125 * buffer will be given to the loader if a loader is
126 * returned. If null is returned the caller may reuse the
127 * buffer.
128 * @return the object loader for the requested object if it is contained
129 * in this pack; null if the object was not found.
130 * @throws IOException
131 * the pack file or the index could not be read.
133 public PackedObjectLoader get(final ObjectId id, final byte[] tmp)
134 throws IOException {
135 final long offset = findOffset(id, tmp);
136 if (offset == -1)
137 return null;
138 final PackedObjectLoader objReader = reader(offset, tmp);
139 objReader.setId(id);
140 return objReader;
143 public void close() throws IOException {
144 pack.close();
145 idx.close();
148 byte[] decompress(final long position, final int totalSize)
149 throws DataFormatException, IOException {
150 final byte[] dstbuf = new byte[totalSize];
151 pack.readCompressed(position, dstbuf);
152 return dstbuf;
155 private void readPackHeader() throws IOException {
156 long position = 0;
157 final byte[] sig = new byte[SIGNATURE.length];
158 final byte[] intbuf = new byte[4];
159 final long vers;
161 if (pack.read(position, sig) != SIGNATURE.length)
162 throw new IOException("Not a PACK file.");
163 for (int k = 0; k < SIGNATURE.length; k++) {
164 if (sig[k] != SIGNATURE[k])
165 throw new IOException("Not a PACK file.");
167 position += SIGNATURE.length;
169 vers = pack.readUInt32(position, intbuf);
170 if (vers != 2 && vers != 3)
171 throw new IOException("Unsupported pack version " + vers + ".");
172 position += 4;
174 objectCnt = pack.readUInt32(position, intbuf);
177 private long[] readIndexHeader() throws CorruptObjectException, IOException {
178 if (idx.length() != (IDX_HDR_LEN + (24 * objectCnt) + (2 * Constants.OBJECT_ID_LENGTH)))
179 throw new CorruptObjectException("Invalid pack index");
181 final long[] idxHeader = new long[256];
182 final byte[] intbuf = new byte[4];
183 for (int k = 0; k < idxHeader.length; k++)
184 idxHeader[k] = idx.readUInt32(k * 4, intbuf);
185 return idxHeader;
188 private PackedObjectLoader reader(final long objOffset, final byte[] ib)
189 throws IOException {
190 long pos = objOffset;
191 int p = 0;
193 pack.readFully(pos, ib);
194 int c = ib[p++] & 0xff;
195 final int typeCode = (c >> 4) & 7;
196 long dataSize = c & 15;
197 int shift = 4;
198 while ((c & 0x80) != 0) {
199 c = ib[p++] & 0xff;
200 dataSize += (c & 0x7f) << shift;
201 shift += 7;
203 pos += p;
205 switch (typeCode) {
206 case Constants.OBJ_COMMIT:
207 return whole(Constants.TYPE_COMMIT, pos, dataSize);
208 case Constants.OBJ_TREE:
209 return whole(Constants.TYPE_TREE, pos, dataSize);
210 case Constants.OBJ_BLOB:
211 return whole(Constants.TYPE_BLOB, pos, dataSize);
212 case Constants.OBJ_TAG:
213 return whole(Constants.TYPE_TAG, pos, dataSize);
214 case Constants.OBJ_OFS_DELTA: {
215 pack.readFully(pos, ib);
216 p = 0;
217 c = ib[p++] & 0xff;
218 long ofs = c & 127;
219 while ((c & 128) != 0) {
220 ofs += 1;
221 c = ib[p++] & 0xff;
222 ofs <<= 7;
223 ofs += (c & 127);
225 return new DeltaOfsPackedObjectLoader(this, pos + p,
226 (int) dataSize, objOffset - ofs);
228 case Constants.OBJ_REF_DELTA: {
229 pack.readFully(pos, ib);
230 return new DeltaRefPackedObjectLoader(this, pos + ib.length,
231 (int) dataSize, new ObjectId(ib));
233 default:
234 throw new IOException("Unknown object type " + typeCode + ".");
238 private final WholePackedObjectLoader whole(final String type,
239 final long pos, final long size) {
240 return new WholePackedObjectLoader(this, pos, type, (int) size);
243 private long findOffset(final ObjectId objId, final byte[] tmpid)
244 throws IOException {
245 final int levelOne = objId.getFirstByte();
246 long high = idxHeader[levelOne];
247 long low = levelOne == 0 ? 0 : idxHeader[levelOne - 1];
249 do {
250 final long mid = (low + high) / 2;
251 final long pos = IDX_HDR_LEN
252 + ((4 + Constants.OBJECT_ID_LENGTH) * mid) + 4;
253 idx.readFully(pos, tmpid);
254 final int cmp = objId.compareTo(tmpid);
255 if (cmp < 0)
256 high = mid;
257 else if (cmp == 0)
258 return idx.readUInt32(pos - 4, tmpid);
259 else
260 low = mid + 1;
261 } while (low < high);
262 return -1;