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