1 /*
2 * Copyright (C) 2008-2009, Google Inc.
3 * Copyright (C) 2008, Jonas Fonseca <fonseca@diku.dk>
4 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
5 * Copyright (C) 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
6 * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org> and others
7 *
8 * This program and the accompanying materials are made available under the
9 * terms of the Eclipse Distribution License v. 1.0 which is available at
10 * https://www.eclipse.org/org/documents/edl-v10.php.
11 *
12 * SPDX-License-Identifier: BSD-3-Clause
13 */
14
15 package org.eclipse.jgit.lib;
16
17 import java.io.EOFException;
18 import java.io.IOException;
19 import java.io.OutputStream;
20
21 import org.eclipse.jgit.errors.LargeObjectException;
22 import org.eclipse.jgit.errors.MissingObjectException;
23 import org.eclipse.jgit.util.IO;
24
25 /**
26 * Base class for a set of loaders for different representations of Git objects.
27 * New loaders are constructed for every object.
28 */
29 public abstract class ObjectLoader {
30 /**
31 * Get Git in pack object type
32 *
33 * @return Git in pack object type, see
34 * {@link org.eclipse.jgit.lib.Constants}.
35 */
36 public abstract int getType();
37
38 /**
39 * Get size of object in bytes
40 *
41 * @return size of object in bytes
42 */
43 public abstract long getSize();
44
45 /**
46 * Whether this object is too large to obtain as a byte array.
47 *
48 * @return true if this object is too large to obtain as a byte array.
49 * Objects over a certain threshold should be accessed only by their
50 * {@link #openStream()} to prevent overflowing the JVM heap.
51 */
52 public boolean isLarge() {
53 try {
54 getCachedBytes();
55 return false;
56 } catch (LargeObjectException tooBig) {
57 return true;
58 }
59 }
60
61 /**
62 * Obtain a copy of the bytes of this object.
63 * <p>
64 * Unlike {@link #getCachedBytes()} this method returns an array that might
65 * be modified by the caller.
66 *
67 * @return the bytes of this object.
68 * @throws org.eclipse.jgit.errors.LargeObjectException
69 * if the object won't fit into a byte array, because
70 * {@link #isLarge()} returns true. Callers should use
71 * {@link #openStream()} instead to access the contents.
72 */
73 public final byte[] getBytes() throws LargeObjectException {
74 return cloneArray(getCachedBytes());
75 }
76
77 /**
78 * Obtain a copy of the bytes of this object.
79 *
80 * If the object size is less than or equal to {@code sizeLimit} this method
81 * will provide it as a byte array, even if {@link #isLarge()} is true. This
82 * utility is useful for application code that absolutely must have the
83 * object as a single contiguous byte array in memory.
84 *
85 * Unlike {@link #getCachedBytes(int)} this method returns an array that
86 * might be modified by the caller.
87 *
88 * @param sizeLimit
89 * maximum number of bytes to return. If the object is larger
90 * than this limit,
91 * {@link org.eclipse.jgit.errors.LargeObjectException} will be
92 * thrown.
93 * @return the bytes of this object.
94 * @throws org.eclipse.jgit.errors.LargeObjectException
95 * if the object is bigger than {@code sizeLimit}, or if
96 * {@link java.lang.OutOfMemoryError} occurs during allocation
97 * of the result array. Callers should use {@link #openStream()}
98 * instead to access the contents.
99 * @throws org.eclipse.jgit.errors.MissingObjectException
100 * the object is large, and it no longer exists.
101 * @throws java.io.IOException
102 * the object store cannot be accessed.
103 */
104 public final byte[] getBytes(int sizeLimit) throws LargeObjectException,
105 MissingObjectException, IOException {
106 byte[] cached = getCachedBytes(sizeLimit);
107 try {
108 return cloneArray(cached);
109 } catch (OutOfMemoryError tooBig) {
110 throw new LargeObjectException.OutOfMemory(tooBig);
111 }
112 }
113
114 /**
115 * Obtain a reference to the (possibly cached) bytes of this object.
116 * <p>
117 * This method offers direct access to the internal caches, potentially
118 * saving on data copies between the internal cache and higher level code.
119 * Callers who receive this reference <b>must not</b> modify its contents.
120 * Changes (if made) will affect the cache but not the repository itself.
121 *
122 * @return the cached bytes of this object. Do not modify it.
123 * @throws org.eclipse.jgit.errors.LargeObjectException
124 * if the object won't fit into a byte array, because
125 * {@link #isLarge()} returns true. Callers should use
126 * {@link #openStream()} instead to access the contents.
127 */
128 public abstract byte[] getCachedBytes() throws LargeObjectException;
129
130 /**
131 * Obtain a reference to the (possibly cached) bytes of this object.
132 *
133 * If the object size is less than or equal to {@code sizeLimit} this method
134 * will provide it as a byte array, even if {@link #isLarge()} is true. This
135 * utility is useful for application code that absolutely must have the
136 * object as a single contiguous byte array in memory.
137 *
138 * This method offers direct access to the internal caches, potentially
139 * saving on data copies between the internal cache and higher level code.
140 * Callers who receive this reference <b>must not</b> modify its contents.
141 * Changes (if made) will affect the cache but not the repository itself.
142 *
143 * @param sizeLimit
144 * maximum number of bytes to return. If the object size is
145 * larger than this limit and {@link #isLarge()} is true,
146 * {@link org.eclipse.jgit.errors.LargeObjectException} will be
147 * thrown.
148 * @return the cached bytes of this object. Do not modify it.
149 * @throws org.eclipse.jgit.errors.LargeObjectException
150 * if the object is bigger than {@code sizeLimit}, or if
151 * {@link java.lang.OutOfMemoryError} occurs during allocation
152 * of the result array. Callers should use {@link #openStream()}
153 * instead to access the contents.
154 * @throws org.eclipse.jgit.errors.MissingObjectException
155 * the object is large, and it no longer exists.
156 * @throws java.io.IOException
157 * the object store cannot be accessed.
158 */
159 public byte[] getCachedBytes(int sizeLimit) throws LargeObjectException,
160 MissingObjectException, IOException {
161 if (!isLarge())
162 return getCachedBytes();
163
164 try (ObjectStream in = openStream()) {
165 long sz = in.getSize();
166 if (sizeLimit < sz)
167 throw new LargeObjectException.ExceedsLimit(sizeLimit, sz);
168
169 if (Integer.MAX_VALUE < sz)
170 throw new LargeObjectException.ExceedsByteArrayLimit();
171
172 byte[] buf;
173 try {
174 buf = new byte[(int) sz];
175 } catch (OutOfMemoryError notEnoughHeap) {
176 throw new LargeObjectException.OutOfMemory(notEnoughHeap);
177 }
178
179 IO.readFully(in, buf, 0, buf.length);
180 return buf;
181 }
182 }
183
184 /**
185 * Obtain an input stream to read this object's data.
186 *
187 * @return a stream of this object's data. Caller must close the stream when
188 * through with it. The returned stream is buffered with a
189 * reasonable buffer size.
190 * @throws org.eclipse.jgit.errors.MissingObjectException
191 * the object no longer exists.
192 * @throws java.io.IOException
193 * the object store cannot be accessed.
194 */
195 public abstract ObjectStream openStream() throws MissingObjectException,
196 IOException;
197
198 /**
199 * Copy this object to the output stream.
200 * <p>
201 * For some object store implementations, this method may be more efficient
202 * than reading from {@link #openStream()} into a temporary byte array, then
203 * writing to the destination stream.
204 * <p>
205 * The default implementation of this method is to copy with a temporary
206 * byte array for large objects, or to pass through the cached byte array
207 * for small objects.
208 *
209 * @param out
210 * stream to receive the complete copy of this object's data.
211 * Caller is responsible for flushing or closing this stream
212 * after this method returns.
213 * @throws org.eclipse.jgit.errors.MissingObjectException
214 * the object no longer exists.
215 * @throws java.io.IOException
216 * the object store cannot be accessed, or the stream cannot be
217 * written to.
218 */
219 public void copyTo(OutputStream out) throws MissingObjectException,
220 IOException {
221 if (isLarge()) {
222 try (ObjectStream in = openStream()) {
223 final long sz = in.getSize();
224 byte[] tmp = new byte[8192];
225 long copied = 0;
226 while (copied < sz) {
227 int n = in.read(tmp);
228 if (n < 0)
229 throw new EOFException();
230 out.write(tmp, 0, n);
231 copied += n;
232 }
233 if (0 <= in.read())
234 throw new EOFException();
235 }
236 } else {
237 out.write(getCachedBytes());
238 }
239 }
240
241 private static byte[] cloneArray(byte[] data) {
242 final byte[] copy = new byte[data.length];
243 System.arraycopy(data, 0, copy, 0, data.length);
244 return copy;
245 }
246
247 /**
248 * Simple loader around the cached byte array.
249 * <p>
250 * ObjectReader implementations can use this stream type when the object's
251 * content is small enough to be accessed as a single byte array.
252 */
253 public static class SmallObject extends ObjectLoader {
254 private final int type;
255
256 private final byte[] data;
257
258 /**
259 * Construct a small object loader.
260 *
261 * @param type
262 * type of the object.
263 * @param data
264 * the object's data array. This array will be returned as-is
265 * for the {@link #getCachedBytes()} method.
266 */
267 public SmallObject(int type, byte[] data) {
268 this.type = type;
269 this.data = data;
270 }
271
272 @Override
273 public int getType() {
274 return type;
275 }
276
277 @Override
278 public long getSize() {
279 return getCachedBytes().length;
280 }
281
282 @Override
283 public boolean isLarge() {
284 return false;
285 }
286
287 @Override
288 public byte[] getCachedBytes() {
289 return data;
290 }
291
292 @Override
293 public ObjectStream openStream() {
294 return new ObjectStream.SmallStream(this);
295 }
296 }
297
298 /**
299 * Wraps a delegate ObjectLoader.
300 *
301 * @since 4.10
302 */
303 public abstract static class Filter extends ObjectLoader {
304 /**
305 * @return delegate ObjectLoader to handle all processing.
306 * @since 4.10
307 */
308 protected abstract ObjectLoader delegate();
309
310 @Override
311 public int getType() {
312 return delegate().getType();
313 }
314
315 @Override
316 public long getSize() {
317 return delegate().getSize();
318 }
319
320 @Override
321 public boolean isLarge() {
322 return delegate().isLarge();
323 }
324
325 @Override
326 public byte[] getCachedBytes() {
327 return delegate().getCachedBytes();
328 }
329
330 @Override
331 public ObjectStream openStream() throws IOException {
332 return delegate().openStream();
333 }
334 }
335 }