View Javadoc
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 }