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  	 * @return Git in pack object type, see {@link Constants}.
65  	 */
66  	public abstract int getType();
67  
68  	/**
69  	 * @return size of object in bytes
70  	 */
71  	public abstract long getSize();
72  
73  	/**
74  	 * @return true if this object is too large to obtain as a byte array.
75  	 *         Objects over a certain threshold should be accessed only by their
76  	 *         {@link #openStream()} to prevent overflowing the JVM heap.
77  	 */
78  	public boolean isLarge() {
79  		try {
80  			getCachedBytes();
81  			return false;
82  		} catch (LargeObjectException tooBig) {
83  			return true;
84  		}
85  	}
86  
87  	/**
88  	 * Obtain a copy of the bytes of this object.
89  	 * <p>
90  	 * Unlike {@link #getCachedBytes()} this method returns an array that might
91  	 * be modified by the caller.
92  	 *
93  	 * @return the bytes of this object.
94  	 * @throws LargeObjectException
95  	 *             if the object won't fit into a byte array, because
96  	 *             {@link #isLarge()} returns true. Callers should use
97  	 *             {@link #openStream()} instead to access the contents.
98  	 */
99  	public final byte[] getBytes() throws LargeObjectException {
100 		return cloneArray(getCachedBytes());
101 	}
102 
103 	/**
104 	 * Obtain a copy of the bytes of this object.
105 	 *
106 	 * If the object size is less than or equal to {@code sizeLimit} this method
107 	 * will provide it as a byte array, even if {@link #isLarge()} is true. This
108 	 * utility is useful for application code that absolutely must have the
109 	 * object as a single contiguous byte array in memory.
110 	 *
111 	 * Unlike {@link #getCachedBytes(int)} this method returns an array that
112 	 * might be modified by the caller.
113 	 *
114 	 * @param sizeLimit
115 	 *            maximum number of bytes to return. If the object is larger
116 	 *            than this limit, {@link LargeObjectException} will be thrown.
117 	 * @return the bytes of this object.
118 	 * @throws LargeObjectException
119 	 *             if the object is bigger than {@code sizeLimit}, or if
120 	 *             {@link OutOfMemoryError} occurs during allocation of the
121 	 *             result array. Callers should use {@link #openStream()}
122 	 *             instead to access the contents.
123 	 * @throws MissingObjectException
124 	 *             the object is large, and it no longer exists.
125 	 * @throws IOException
126 	 *             the object store cannot be accessed.
127 	 */
128 	public final byte[] getBytes(int sizeLimit) throws LargeObjectException,
129 			MissingObjectException, IOException {
130 		byte[] cached = getCachedBytes(sizeLimit);
131 		try {
132 			return cloneArray(cached);
133 		} catch (OutOfMemoryError tooBig) {
134 			throw new LargeObjectException.OutOfMemory(tooBig);
135 		}
136 	}
137 
138 	/**
139 	 * Obtain a reference to the (possibly cached) bytes of this object.
140 	 * <p>
141 	 * This method offers direct access to the internal caches, potentially
142 	 * saving on data copies between the internal cache and higher level code.
143 	 * Callers who receive this reference <b>must not</b> modify its contents.
144 	 * Changes (if made) will affect the cache but not the repository itself.
145 	 *
146 	 * @return the cached bytes of this object. Do not modify it.
147 	 * @throws LargeObjectException
148 	 *             if the object won't fit into a byte array, because
149 	 *             {@link #isLarge()} returns true. Callers should use
150 	 *             {@link #openStream()} instead to access the contents.
151 	 */
152 	public abstract byte[] getCachedBytes() throws LargeObjectException;
153 
154 	/**
155 	 * Obtain a reference to the (possibly cached) bytes of this object.
156 	 *
157 	 * If the object size is less than or equal to {@code sizeLimit} this method
158 	 * will provide it as a byte array, even if {@link #isLarge()} is true. This
159 	 * utility is useful for application code that absolutely must have the
160 	 * object as a single contiguous byte array in memory.
161 	 *
162 	 * This method offers direct access to the internal caches, potentially
163 	 * saving on data copies between the internal cache and higher level code.
164 	 * Callers who receive this reference <b>must not</b> modify its contents.
165 	 * Changes (if made) will affect the cache but not the repository itself.
166 	 *
167 	 * @param sizeLimit
168 	 *            maximum number of bytes to return. If the object size is
169 	 *            larger than this limit and {@link #isLarge()} is true,
170 	 *            {@link LargeObjectException} will be thrown.
171 	 * @return the cached bytes of this object. Do not modify it.
172 	 * @throws LargeObjectException
173 	 *             if the object is bigger than {@code sizeLimit}, or if
174 	 *             {@link OutOfMemoryError} occurs during allocation of the
175 	 *             result array. Callers should use {@link #openStream()}
176 	 *             instead to access the contents.
177 	 * @throws MissingObjectException
178 	 *             the object is large, and it no longer exists.
179 	 * @throws IOException
180 	 *             the object store cannot be accessed.
181 	 */
182 	public byte[] getCachedBytes(int sizeLimit) throws LargeObjectException,
183 			MissingObjectException, IOException {
184 		if (!isLarge())
185 			return getCachedBytes();
186 
187 		ObjectStream in = openStream();
188 		try {
189 			long sz = in.getSize();
190 			if (sizeLimit < sz)
191 				throw new LargeObjectException.ExceedsLimit(sizeLimit, sz);
192 
193 			if (Integer.MAX_VALUE < sz)
194 				throw new LargeObjectException.ExceedsByteArrayLimit();
195 
196 			byte[] buf;
197 			try {
198 				buf = new byte[(int) sz];
199 			} catch (OutOfMemoryError notEnoughHeap) {
200 				throw new LargeObjectException.OutOfMemory(notEnoughHeap);
201 			}
202 
203 			IO.readFully(in, buf, 0, buf.length);
204 			return buf;
205 		} finally {
206 			in.close();
207 		}
208 	}
209 
210 	/**
211 	 * Obtain an input stream to read this object's data.
212 	 *
213 	 * @return a stream of this object's data. Caller must close the stream when
214 	 *         through with it. The returned stream is buffered with a
215 	 *         reasonable buffer size.
216 	 * @throws MissingObjectException
217 	 *             the object no longer exists.
218 	 * @throws IOException
219 	 *             the object store cannot be accessed.
220 	 */
221 	public abstract ObjectStream openStream() throws MissingObjectException,
222 			IOException;
223 
224 	/**
225 	 * Copy this object to the output stream.
226 	 * <p>
227 	 * For some object store implementations, this method may be more efficient
228 	 * than reading from {@link #openStream()} into a temporary byte array, then
229 	 * writing to the destination stream.
230 	 * <p>
231 	 * The default implementation of this method is to copy with a temporary
232 	 * byte array for large objects, or to pass through the cached byte array
233 	 * for small objects.
234 	 *
235 	 * @param out
236 	 *            stream to receive the complete copy of this object's data.
237 	 *            Caller is responsible for flushing or closing this stream
238 	 *            after this method returns.
239 	 * @throws MissingObjectException
240 	 *             the object no longer exists.
241 	 * @throws IOException
242 	 *             the object store cannot be accessed, or the stream cannot be
243 	 *             written to.
244 	 */
245 	public void copyTo(OutputStream out) throws MissingObjectException,
246 			IOException {
247 		if (isLarge()) {
248 			ObjectStream in = openStream();
249 			try {
250 				final long sz = in.getSize();
251 				byte[] tmp = new byte[8192];
252 				long copied = 0;
253 				while (copied < sz) {
254 					int n = in.read(tmp);
255 					if (n < 0)
256 						throw new EOFException();
257 					out.write(tmp, 0, n);
258 					copied += n;
259 				}
260 				if (0 <= in.read())
261 					throw new EOFException();
262 			} finally {
263 				in.close();
264 			}
265 		} else {
266 			out.write(getCachedBytes());
267 		}
268 	}
269 
270 	private static byte[] cloneArray(final byte[] data) {
271 		final byte[] copy = new byte[data.length];
272 		System.arraycopy(data, 0, copy, 0, data.length);
273 		return copy;
274 	}
275 
276 	/**
277 	 * Simple loader around the cached byte array.
278 	 * <p>
279 	 * ObjectReader implementations can use this stream type when the object's
280 	 * content is small enough to be accessed as a single byte array.
281 	 */
282 	public static class SmallObject extends ObjectLoader {
283 		private final int type;
284 
285 		private final byte[] data;
286 
287 		/**
288 		 * Construct a small object loader.
289 		 *
290 		 * @param type
291 		 *            type of the object.
292 		 * @param data
293 		 *            the object's data array. This array will be returned as-is
294 		 *            for the {@link #getCachedBytes()} method.
295 		 */
296 		public SmallObject(int type, byte[] data) {
297 			this.type = type;
298 			this.data = data;
299 		}
300 
301 		@Override
302 		public int getType() {
303 			return type;
304 		}
305 
306 		@Override
307 		public long getSize() {
308 			return getCachedBytes().length;
309 		}
310 
311 		@Override
312 		public boolean isLarge() {
313 			return false;
314 		}
315 
316 		@Override
317 		public byte[] getCachedBytes() {
318 			return data;
319 		}
320 
321 		@Override
322 		public ObjectStream openStream() {
323 			return new ObjectStream.SmallStream(this);
324 		}
325 	}
326 }