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