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 }