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>
7 * and other copyright owners as documented in the project's IP log.
8 *
9 * This program and the accompanying materials are made available
10 * under the terms of the Eclipse Distribution License v1.0 which
11 * accompanies this distribution, is reproduced below, and is
12 * available at http://www.eclipse.org/org/documents/edl-v10.php
13 *
14 * All rights reserved.
15 *
16 * Redistribution and use in source and binary forms, with or
17 * without modification, are permitted provided that the following
18 * conditions are met:
19 *
20 * - Redistributions of source code must retain the above copyright
21 * notice, this list of conditions and the following disclaimer.
22 *
23 * - Redistributions in binary form must reproduce the above
24 * copyright notice, this list of conditions and the following
25 * disclaimer in the documentation and/or other materials provided
26 * with the distribution.
27 *
28 * - Neither the name of the Eclipse Foundation, Inc. nor the
29 * names of its contributors may be used to endorse or promote
30 * products derived from this software without specific prior
31 * written permission.
32 *
33 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
34 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
35 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
36 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
38 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
39 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
40 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
41 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
42 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
43 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
44 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
45 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46 */
47
48 package org.eclipse.jgit.lib;
49
50 import java.io.EOFException;
51 import java.io.IOException;
52 import java.io.OutputStream;
53
54 import org.eclipse.jgit.errors.LargeObjectException;
55 import org.eclipse.jgit.errors.MissingObjectException;
56 import org.eclipse.jgit.util.IO;
57
58 /**
59 * Base class for a set of loaders for different representations of Git objects.
60 * New loaders are constructed for every object.
61 */
62 public abstract class ObjectLoader {
63 /**
64 * Get Git in pack object type
65 *
66 * @return Git in pack object type, see
67 * {@link org.eclipse.jgit.lib.Constants}.
68 */
69 public abstract int getType();
70
71 /**
72 * Get size of object in bytes
73 *
74 * @return size of object in bytes
75 */
76 public abstract long getSize();
77
78 /**
79 * Whether this object is too large to obtain as a byte array.
80 *
81 * @return true if this object is too large to obtain as a byte array.
82 * Objects over a certain threshold should be accessed only by their
83 * {@link #openStream()} to prevent overflowing the JVM heap.
84 */
85 public boolean isLarge() {
86 try {
87 getCachedBytes();
88 return false;
89 } catch (LargeObjectException tooBig) {
90 return true;
91 }
92 }
93
94 /**
95 * Obtain a copy of the bytes of this object.
96 * <p>
97 * Unlike {@link #getCachedBytes()} this method returns an array that might
98 * be modified by the caller.
99 *
100 * @return the bytes of this object.
101 * @throws org.eclipse.jgit.errors.LargeObjectException
102 * if the object won't fit into a byte array, because
103 * {@link #isLarge()} returns true. Callers should use
104 * {@link #openStream()} instead to access the contents.
105 */
106 public final byte[] getBytes() throws LargeObjectException {
107 return cloneArray(getCachedBytes());
108 }
109
110 /**
111 * Obtain a copy of the bytes of this object.
112 *
113 * If the object size is less than or equal to {@code sizeLimit} this method
114 * will provide it as a byte array, even if {@link #isLarge()} is true. This
115 * utility is useful for application code that absolutely must have the
116 * object as a single contiguous byte array in memory.
117 *
118 * Unlike {@link #getCachedBytes(int)} this method returns an array that
119 * might be modified by the caller.
120 *
121 * @param sizeLimit
122 * maximum number of bytes to return. If the object is larger
123 * than this limit,
124 * {@link org.eclipse.jgit.errors.LargeObjectException} will be
125 * thrown.
126 * @return the bytes of this object.
127 * @throws org.eclipse.jgit.errors.LargeObjectException
128 * if the object is bigger than {@code sizeLimit}, or if
129 * {@link java.lang.OutOfMemoryError} occurs during allocation
130 * of the result array. Callers should use {@link #openStream()}
131 * instead to access the contents.
132 * @throws org.eclipse.jgit.errors.MissingObjectException
133 * the object is large, and it no longer exists.
134 * @throws java.io.IOException
135 * the object store cannot be accessed.
136 */
137 public final byte[] getBytes(int sizeLimit) throws LargeObjectException,
138 MissingObjectException, IOException {
139 byte[] cached = getCachedBytes(sizeLimit);
140 try {
141 return cloneArray(cached);
142 } catch (OutOfMemoryError tooBig) {
143 throw new LargeObjectException.OutOfMemory(tooBig);
144 }
145 }
146
147 /**
148 * Obtain a reference to the (possibly cached) bytes of this object.
149 * <p>
150 * This method offers direct access to the internal caches, potentially
151 * saving on data copies between the internal cache and higher level code.
152 * Callers who receive this reference <b>must not</b> modify its contents.
153 * Changes (if made) will affect the cache but not the repository itself.
154 *
155 * @return the cached bytes of this object. Do not modify it.
156 * @throws org.eclipse.jgit.errors.LargeObjectException
157 * if the object won't fit into a byte array, because
158 * {@link #isLarge()} returns true. Callers should use
159 * {@link #openStream()} instead to access the contents.
160 */
161 public abstract byte[] getCachedBytes() throws LargeObjectException;
162
163 /**
164 * Obtain a reference to the (possibly cached) bytes of this object.
165 *
166 * If the object size is less than or equal to {@code sizeLimit} this method
167 * will provide it as a byte array, even if {@link #isLarge()} is true. This
168 * utility is useful for application code that absolutely must have the
169 * object as a single contiguous byte array in memory.
170 *
171 * This method offers direct access to the internal caches, potentially
172 * saving on data copies between the internal cache and higher level code.
173 * Callers who receive this reference <b>must not</b> modify its contents.
174 * Changes (if made) will affect the cache but not the repository itself.
175 *
176 * @param sizeLimit
177 * maximum number of bytes to return. If the object size is
178 * larger than this limit and {@link #isLarge()} is true,
179 * {@link org.eclipse.jgit.errors.LargeObjectException} will be
180 * thrown.
181 * @return the cached bytes of this object. Do not modify it.
182 * @throws org.eclipse.jgit.errors.LargeObjectException
183 * if the object is bigger than {@code sizeLimit}, or if
184 * {@link java.lang.OutOfMemoryError} occurs during allocation
185 * of the result array. Callers should use {@link #openStream()}
186 * instead to access the contents.
187 * @throws org.eclipse.jgit.errors.MissingObjectException
188 * the object is large, and it no longer exists.
189 * @throws java.io.IOException
190 * the object store cannot be accessed.
191 */
192 public byte[] getCachedBytes(int sizeLimit) throws LargeObjectException,
193 MissingObjectException, IOException {
194 if (!isLarge())
195 return getCachedBytes();
196
197 try (ObjectStream in = openStream()) {
198 long sz = in.getSize();
199 if (sizeLimit < sz)
200 throw new LargeObjectException.ExceedsLimit(sizeLimit, sz);
201
202 if (Integer.MAX_VALUE < sz)
203 throw new LargeObjectException.ExceedsByteArrayLimit();
204
205 byte[] buf;
206 try {
207 buf = new byte[(int) sz];
208 } catch (OutOfMemoryError notEnoughHeap) {
209 throw new LargeObjectException.OutOfMemory(notEnoughHeap);
210 }
211
212 IO.readFully(in, buf, 0, buf.length);
213 return buf;
214 }
215 }
216
217 /**
218 * Obtain an input stream to read this object's data.
219 *
220 * @return a stream of this object's data. Caller must close the stream when
221 * through with it. The returned stream is buffered with a
222 * reasonable buffer size.
223 * @throws org.eclipse.jgit.errors.MissingObjectException
224 * the object no longer exists.
225 * @throws java.io.IOException
226 * the object store cannot be accessed.
227 */
228 public abstract ObjectStream openStream() throws MissingObjectException,
229 IOException;
230
231 /**
232 * Copy this object to the output stream.
233 * <p>
234 * For some object store implementations, this method may be more efficient
235 * than reading from {@link #openStream()} into a temporary byte array, then
236 * writing to the destination stream.
237 * <p>
238 * The default implementation of this method is to copy with a temporary
239 * byte array for large objects, or to pass through the cached byte array
240 * for small objects.
241 *
242 * @param out
243 * stream to receive the complete copy of this object's data.
244 * Caller is responsible for flushing or closing this stream
245 * after this method returns.
246 * @throws org.eclipse.jgit.errors.MissingObjectException
247 * the object no longer exists.
248 * @throws java.io.IOException
249 * the object store cannot be accessed, or the stream cannot be
250 * written to.
251 */
252 public void copyTo(OutputStream out) throws MissingObjectException,
253 IOException {
254 if (isLarge()) {
255 try (ObjectStream in = openStream()) {
256 final long sz = in.getSize();
257 byte[] tmp = new byte[8192];
258 long copied = 0;
259 while (copied < sz) {
260 int n = in.read(tmp);
261 if (n < 0)
262 throw new EOFException();
263 out.write(tmp, 0, n);
264 copied += n;
265 }
266 if (0 <= in.read())
267 throw new EOFException();
268 }
269 } else {
270 out.write(getCachedBytes());
271 }
272 }
273
274 private static byte[] cloneArray(byte[] data) {
275 final byte[] copy = new byte[data.length];
276 System.arraycopy(data, 0, copy, 0, data.length);
277 return copy;
278 }
279
280 /**
281 * Simple loader around the cached byte array.
282 * <p>
283 * ObjectReader implementations can use this stream type when the object's
284 * content is small enough to be accessed as a single byte array.
285 */
286 public static class SmallObject extends ObjectLoader {
287 private final int type;
288
289 private final byte[] data;
290
291 /**
292 * Construct a small object loader.
293 *
294 * @param type
295 * type of the object.
296 * @param data
297 * the object's data array. This array will be returned as-is
298 * for the {@link #getCachedBytes()} method.
299 */
300 public SmallObject(int type, byte[] data) {
301 this.type = type;
302 this.data = data;
303 }
304
305 @Override
306 public int getType() {
307 return type;
308 }
309
310 @Override
311 public long getSize() {
312 return getCachedBytes().length;
313 }
314
315 @Override
316 public boolean isLarge() {
317 return false;
318 }
319
320 @Override
321 public byte[] getCachedBytes() {
322 return data;
323 }
324
325 @Override
326 public ObjectStream openStream() {
327 return new ObjectStream.SmallStream(this);
328 }
329 }
330
331 /**
332 * Wraps a delegate ObjectLoader.
333 *
334 * @since 4.10
335 */
336 public static abstract class Filter extends ObjectLoader {
337 /**
338 * @return delegate ObjectLoader to handle all processing.
339 * @since 4.10
340 */
341 protected abstract ObjectLoader delegate();
342
343 @Override
344 public int getType() {
345 return delegate().getType();
346 }
347
348 @Override
349 public long getSize() {
350 return delegate().getSize();
351 }
352
353 @Override
354 public boolean isLarge() {
355 return delegate().isLarge();
356 }
357
358 @Override
359 public byte[] getCachedBytes() {
360 return delegate().getCachedBytes();
361 }
362
363 @Override
364 public ObjectStream openStream() throws IOException {
365 return delegate().openStream();
366 }
367 }
368 }