View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.internal.storage.file;
13  
14  import java.io.IOException;
15  import java.util.Collection;
16  import java.util.Collections;
17  import java.util.HashSet;
18  import java.util.List;
19  import java.util.Set;
20  import java.util.zip.DataFormatException;
21  import java.util.zip.Inflater;
22  
23  import org.eclipse.jgit.annotations.Nullable;
24  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
25  import org.eclipse.jgit.errors.MissingObjectException;
26  import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
27  import org.eclipse.jgit.internal.JGitText;
28  import org.eclipse.jgit.internal.storage.pack.CachedPack;
29  import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs;
30  import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
31  import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
32  import org.eclipse.jgit.internal.storage.pack.PackWriter;
33  import org.eclipse.jgit.lib.AbbreviatedObjectId;
34  import org.eclipse.jgit.lib.AnyObjectId;
35  import org.eclipse.jgit.lib.BitmapIndex;
36  import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder;
37  import org.eclipse.jgit.lib.Constants;
38  import org.eclipse.jgit.lib.InflaterCache;
39  import org.eclipse.jgit.lib.ObjectId;
40  import org.eclipse.jgit.lib.ObjectInserter;
41  import org.eclipse.jgit.lib.ObjectLoader;
42  import org.eclipse.jgit.lib.ObjectReader;
43  import org.eclipse.jgit.lib.ProgressMonitor;
44  
45  /** Active handle to a ByteWindow. */
46  final class WindowCursor extends ObjectReader implements ObjectReuseAsIs {
47  	/** Temporary buffer large enough for at least one raw object id. */
48  	final byte[] tempId = new byte[Constants.OBJECT_ID_LENGTH];
49  
50  	private Inflater inf;
51  
52  	private ByteWindow window;
53  
54  	private DeltaBaseCache baseCache;
55  
56  	@Nullable
57  	private final ObjectInserter createdFromInserter;
58  
59  	final FileObjectDatabase db;
60  
61  	WindowCursor(FileObjectDatabase db) {
62  		this.db = db;
63  		this.createdFromInserter = null;
64  		this.streamFileThreshold = WindowCache.getStreamFileThreshold();
65  	}
66  
67  	WindowCursor(FileObjectDatabase db,
68  			@Nullable ObjectDirectoryInserter createdFromInserter) {
69  		this.db = db;
70  		this.createdFromInserter = createdFromInserter;
71  		this.streamFileThreshold = WindowCache.getStreamFileThreshold();
72  	}
73  
74  	DeltaBaseCache getDeltaBaseCache() {
75  		if (baseCache == null)
76  			baseCache = new DeltaBaseCache();
77  		return baseCache;
78  	}
79  
80  	/** {@inheritDoc} */
81  	@Override
82  	public ObjectReader newReader() {
83  		return new WindowCursor(db);
84  	}
85  
86  	/** {@inheritDoc} */
87  	@Override
88  	public BitmapIndex getBitmapIndex() throws IOException {
89  		for (PackFile pack : db.getPacks()) {
90  			PackBitmapIndex index = pack.getBitmapIndex();
91  			if (index != null)
92  				return new BitmapIndexImpl(index);
93  		}
94  		return null;
95  	}
96  
97  	/** {@inheritDoc} */
98  	@Override
99  	public Collection<CachedPack> getCachedPacksAndUpdate(
100 			BitmapBuilder needBitmap) throws IOException {
101 		for (PackFile pack : db.getPacks()) {
102 			PackBitmapIndex index = pack.getBitmapIndex();
103 			if (needBitmap.removeAllOrNone(index))
104 				return Collections.<CachedPack> singletonList(
105 						new LocalCachedPack(Collections.singletonList(pack)));
106 		}
107 		return Collections.emptyList();
108 	}
109 
110 	/** {@inheritDoc} */
111 	@Override
112 	public Collection<ObjectId> resolve(AbbreviatedObjectId id)
113 			throws IOException {
114 		if (id.isComplete())
115 			return Collections.singleton(id.toObjectId());
116 		HashSet<ObjectId> matches = new HashSet<>(4);
117 		db.resolve(matches, id);
118 		return matches;
119 	}
120 
121 	/** {@inheritDoc} */
122 	@Override
123 	public boolean has(AnyObjectId objectId) throws IOException {
124 		return db.has(objectId);
125 	}
126 
127 	/** {@inheritDoc} */
128 	@Override
129 	public ObjectLoader open(AnyObjectId objectId, int typeHint)
130 			throws MissingObjectException, IncorrectObjectTypeException,
131 			IOException {
132 		final ObjectLoader ldr = db.openObject(this, objectId);
133 		if (ldr == null) {
134 			if (typeHint == OBJ_ANY)
135 				throw new MissingObjectException(objectId.copy(),
136 						JGitText.get().unknownObjectType2);
137 			throw new MissingObjectException(objectId.copy(), typeHint);
138 		}
139 		if (typeHint != OBJ_ANY && ldr.getType() != typeHint)
140 			throw new IncorrectObjectTypeException(objectId.copy(), typeHint);
141 		return ldr;
142 	}
143 
144 	/** {@inheritDoc} */
145 	@Override
146 	public Set<ObjectId> getShallowCommits() throws IOException {
147 		return db.getShallowCommits();
148 	}
149 
150 	/** {@inheritDoc} */
151 	@Override
152 	public long getObjectSize(AnyObjectId objectId, int typeHint)
153 			throws MissingObjectException, IncorrectObjectTypeException,
154 			IOException {
155 		long sz = db.getObjectSize(this, objectId);
156 		if (sz < 0) {
157 			if (typeHint == OBJ_ANY)
158 				throw new MissingObjectException(objectId.copy(),
159 						JGitText.get().unknownObjectType2);
160 			throw new MissingObjectException(objectId.copy(), typeHint);
161 		}
162 		return sz;
163 	}
164 
165 	/** {@inheritDoc} */
166 	@Override
167 	public LocalObjectToPack newObjectToPack(AnyObjectId objectId, int type) {
168 		return new LocalObjectToPack(objectId, type);
169 	}
170 
171 	/** {@inheritDoc} */
172 	@Override
173 	public void selectObjectRepresentation(PackWriter packer,
174 			ProgressMonitor monitor, Iterable<ObjectToPack> objects)
175 			throws IOException, MissingObjectException {
176 		for (ObjectToPack otp : objects) {
177 			db.selectObjectRepresentation(packer, otp, this);
178 			monitor.update(1);
179 		}
180 	}
181 
182 	/** {@inheritDoc} */
183 	@Override
184 	public void copyObjectAsIs(PackOutputStream out, ObjectToPack otp,
185 			boolean validate) throws IOException,
186 			StoredObjectRepresentationNotAvailableException {
187 		LocalObjectToPack src = (LocalObjectToPack) otp;
188 		src.pack.copyAsIs(out, src, validate, this);
189 	}
190 
191 	/** {@inheritDoc} */
192 	@Override
193 	public void writeObjects(PackOutputStream out, List<ObjectToPack> list)
194 			throws IOException {
195 		for (ObjectToPack otp : list)
196 			out.writeObject(otp);
197 	}
198 
199 	/**
200 	 * Copy bytes from the window to a caller supplied buffer.
201 	 *
202 	 * @param pack
203 	 *            the file the desired window is stored within.
204 	 * @param position
205 	 *            position within the file to read from.
206 	 * @param dstbuf
207 	 *            destination buffer to copy into.
208 	 * @param dstoff
209 	 *            offset within <code>dstbuf</code> to start copying into.
210 	 * @param cnt
211 	 *            number of bytes to copy. This value may exceed the number of
212 	 *            bytes remaining in the window starting at offset
213 	 *            <code>pos</code>.
214 	 * @return number of bytes actually copied; this may be less than
215 	 *         <code>cnt</code> if <code>cnt</code> exceeded the number of bytes
216 	 *         available.
217 	 * @throws IOException
218 	 *             this cursor does not match the provider or id and the proper
219 	 *             window could not be acquired through the provider's cache.
220 	 */
221 	int copy(final PackFile pack, long position, final byte[] dstbuf,
222 			int dstoff, final int cnt) throws IOException {
223 		final long length = pack.length;
224 		int need = cnt;
225 		while (need > 0 && position < length) {
226 			pin(pack, position);
227 			final int r = window.copy(position, dstbuf, dstoff, need);
228 			position += r;
229 			dstoff += r;
230 			need -= r;
231 		}
232 		return cnt - need;
233 	}
234 
235 	/** {@inheritDoc} */
236 	@Override
237 	public void copyPackAsIs(PackOutputStream out, CachedPack pack)
238 			throws IOException {
239 		((LocalCachedPack) pack).copyAsIs(out, this);
240 	}
241 
242 	void copyPackAsIs(final PackFile pack, final long length,
243 			final PackOutputStream out) throws IOException {
244 		long position = 12;
245 		long remaining = length - (12 + 20);
246 		while (0 < remaining) {
247 			pin(pack, position);
248 
249 			int ptr = (int) (position - window.start);
250 			int n = (int) Math.min(window.size() - ptr, remaining);
251 			window.write(out, position, n);
252 			position += n;
253 			remaining -= n;
254 		}
255 	}
256 
257 	/**
258 	 * Inflate a region of the pack starting at {@code position}.
259 	 *
260 	 * @param pack
261 	 *            the file the desired window is stored within.
262 	 * @param position
263 	 *            position within the file to read from.
264 	 * @param dstbuf
265 	 *            destination buffer the inflater should output decompressed
266 	 *            data to. Must be large enough to store the entire stream,
267 	 *            unless headerOnly is true.
268 	 * @param headerOnly
269 	 *            if true the caller wants only {@code dstbuf.length} bytes.
270 	 * @return number of bytes inflated into <code>dstbuf</code>.
271 	 * @throws IOException
272 	 *             this cursor does not match the provider or id and the proper
273 	 *             window could not be acquired through the provider's cache.
274 	 * @throws DataFormatException
275 	 *             the inflater encountered an invalid chunk of data. Data
276 	 *             stream corruption is likely.
277 	 */
278 	int inflate(final PackFile pack, long position, final byte[] dstbuf,
279 			boolean headerOnly) throws IOException, DataFormatException {
280 		prepareInflater();
281 		pin(pack, position);
282 		position += window.setInput(position, inf);
283 		for (int dstoff = 0;;) {
284 			int n = inf.inflate(dstbuf, dstoff, dstbuf.length - dstoff);
285 			dstoff += n;
286 			if (inf.finished() || (headerOnly && dstoff == dstbuf.length))
287 				return dstoff;
288 			if (inf.needsInput()) {
289 				pin(pack, position);
290 				position += window.setInput(position, inf);
291 			} else if (n == 0)
292 				throw new DataFormatException();
293 		}
294 	}
295 
296 	ByteArrayWindow quickCopy(PackFile p, long pos, long cnt)
297 			throws IOException {
298 		pin(p, pos);
299 		if (window instanceof ByteArrayWindow
300 				&& window.contains(p, pos + (cnt - 1)))
301 			return (ByteArrayWindow) window;
302 		return null;
303 	}
304 
305 	Inflater inflater() {
306 		prepareInflater();
307 		return inf;
308 	}
309 
310 	private void prepareInflater() {
311 		if (inf == null)
312 			inf = InflaterCache.get();
313 		else
314 			inf.reset();
315 	}
316 
317 	void pin(PackFile pack, long position)
318 			throws IOException {
319 		final ByteWindow w = window;
320 		if (w == null || !w.contains(pack, position)) {
321 			// If memory is low, we may need what is in our window field to
322 			// be cleaned up by the GC during the get for the next window.
323 			// So we always clear it, even though we are just going to set
324 			// it again.
325 			//
326 			window = null;
327 			window = WindowCache.get(pack, position);
328 		}
329 	}
330 
331 	/** {@inheritDoc} */
332 	@Override
333 	@Nullable
334 	public ObjectInserter getCreatedFromInserter() {
335 		return createdFromInserter;
336 	}
337 
338 	/**
339 	 * {@inheritDoc}
340 	 * <p>
341 	 * Release the current window cursor.
342 	 */
343 	@Override
344 	public void close() {
345 		window = null;
346 		baseCache = null;
347 		try {
348 			InflaterCache.release(inf);
349 		} finally {
350 			inf = null;
351 		}
352 	}
353 }