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 ObjectStream in = openStream();
198 try {
199 long sz = in.getSize();
200 if (sizeLimit < sz)
201 throw new LargeObjectException.ExceedsLimit(sizeLimit, sz);
202
203 if (Integer.MAX_VALUE < sz)
204 throw new LargeObjectException.ExceedsByteArrayLimit();
205
206 byte[] buf;
207 try {
208 buf = new byte[(int) sz];
209 } catch (OutOfMemoryError notEnoughHeap) {
210 throw new LargeObjectException.OutOfMemory(notEnoughHeap);
211 }
212
213 IO.readFully(in, buf, 0, buf.length);
214 return buf;
215 } finally {
216 in.close();
217 }
218 }
219
220 /**
221 * Obtain an input stream to read this object's data.
222 *
223 * @return a stream of this object's data. Caller must close the stream when
224 * through with it. The returned stream is buffered with a
225 * reasonable buffer size.
226 * @throws org.eclipse.jgit.errors.MissingObjectException
227 * the object no longer exists.
228 * @throws java.io.IOException
229 * the object store cannot be accessed.
230 */
231 public abstract ObjectStream openStream() throws MissingObjectException,
232 IOException;
233
234 /**
235 * Copy this object to the output stream.
236 * <p>
237 * For some object store implementations, this method may be more efficient
238 * than reading from {@link #openStream()} into a temporary byte array, then
239 * writing to the destination stream.
240 * <p>
241 * The default implementation of this method is to copy with a temporary
242 * byte array for large objects, or to pass through the cached byte array
243 * for small objects.
244 *
245 * @param out
246 * stream to receive the complete copy of this object's data.
247 * Caller is responsible for flushing or closing this stream
248 * after this method returns.
249 * @throws org.eclipse.jgit.errors.MissingObjectException
250 * the object no longer exists.
251 * @throws java.io.IOException
252 * the object store cannot be accessed, or the stream cannot be
253 * written to.
254 */
255 public void copyTo(OutputStream out) throws MissingObjectException,
256 IOException {
257 if (isLarge()) {
258 ObjectStream in = openStream();
259 try {
260 final long sz = in.getSize();
261 byte[] tmp = new byte[8192];
262 long copied = 0;
263 while (copied < sz) {
264 int n = in.read(tmp);
265 if (n < 0)
266 throw new EOFException();
267 out.write(tmp, 0, n);
268 copied += n;
269 }
270 if (0 <= in.read())
271 throw new EOFException();
272 } finally {
273 in.close();
274 }
275 } else {
276 out.write(getCachedBytes());
277 }
278 }
279
280 private static byte[] cloneArray(final byte[] data) {
281 final byte[] copy = new byte[data.length];
282 System.arraycopy(data, 0, copy, 0, data.length);
283 return copy;
284 }
285
286 /**
287 * Simple loader around the cached byte array.
288 * <p>
289 * ObjectReader implementations can use this stream type when the object's
290 * content is small enough to be accessed as a single byte array.
291 */
292 public static class SmallObject extends ObjectLoader {
293 private final int type;
294
295 private final byte[] data;
296
297 /**
298 * Construct a small object loader.
299 *
300 * @param type
301 * type of the object.
302 * @param data
303 * the object's data array. This array will be returned as-is
304 * for the {@link #getCachedBytes()} method.
305 */
306 public SmallObject(int type, byte[] data) {
307 this.type = type;
308 this.data = data;
309 }
310
311 @Override
312 public int getType() {
313 return type;
314 }
315
316 @Override
317 public long getSize() {
318 return getCachedBytes().length;
319 }
320
321 @Override
322 public boolean isLarge() {
323 return false;
324 }
325
326 @Override
327 public byte[] getCachedBytes() {
328 return data;
329 }
330
331 @Override
332 public ObjectStream openStream() {
333 return new ObjectStream.SmallStream(this);
334 }
335 }
336
337 /**
338 * Wraps a delegate ObjectLoader.
339 *
340 * @since 4.10
341 */
342 public static abstract class Filter extends ObjectLoader {
343 /**
344 * @return delegate ObjectLoader to handle all processing.
345 * @since 4.10
346 */
347 protected abstract ObjectLoader delegate();
348
349 @Override
350 public int getType() {
351 return delegate().getType();
352 }
353
354 @Override
355 public long getSize() {
356 return delegate().getSize();
357 }
358
359 @Override
360 public boolean isLarge() {
361 return delegate().isLarge();
362 }
363
364 @Override
365 public byte[] getCachedBytes() {
366 return delegate().getCachedBytes();
367 }
368
369 @Override
370 public ObjectStream openStream() throws IOException {
371 return delegate().openStream();
372 }
373 }
374 }