View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.internal.storage.pack;
13  
14  import static org.eclipse.jgit.lib.Constants.OBJ_OFS_DELTA;
15  import static org.eclipse.jgit.lib.Constants.OBJ_REF_DELTA;
16  import static org.eclipse.jgit.lib.Constants.PACK_SIGNATURE;
17  
18  import java.io.IOException;
19  import java.io.OutputStream;
20  
21  import org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream;
22  import org.eclipse.jgit.lib.ProgressMonitor;
23  import org.eclipse.jgit.util.NB;
24  
25  /**
26   * Custom output stream to support
27   * {@link org.eclipse.jgit.internal.storage.pack.PackWriter}.
28   */
29  public final class PackOutputStream extends CancellableDigestOutputStream {
30  
31  	private final PackWriter packWriter;
32  
33  	private final byte[] headerBuffer = new byte[32];
34  
35  	private final byte[] copyBuffer = new byte[64 << 10];
36  
37  	private boolean ofsDelta;
38  
39  	/**
40  	 * Initialize a pack output stream.
41  	 * <p>
42  	 * This constructor is exposed to support debugging the JGit library only.
43  	 * Application or storage level code should not create a PackOutputStream,
44  	 * instead use {@link org.eclipse.jgit.internal.storage.pack.PackWriter},
45  	 * and let the writer create the stream.
46  	 *
47  	 * @param writeMonitor
48  	 *            monitor to update on object output progress.
49  	 * @param out
50  	 *            target stream to receive all object contents.
51  	 * @param pw
52  	 *            packer that is going to perform the output.
53  	 */
54  	public PackOutputStream(final ProgressMonitor writeMonitor,
55  			final OutputStream out, final PackWriter pw) {
56  		super(writeMonitor, out);
57  		this.packWriter = pw;
58  	}
59  
60  	final void writeFileHeader(int version, long objectCount)
61  			throws IOException {
62  		System.arraycopy(PACK_SIGNATURE, 0, headerBuffer, 0, 4);
63  		NB.encodeInt32(headerBuffer, 4, version);
64  		NB.encodeInt32(headerBuffer, 8, (int) objectCount);
65  		write(headerBuffer, 0, 12);
66  		ofsDelta = packWriter.isDeltaBaseAsOffset();
67  	}
68  
69  	/**
70  	 * Write one object.
71  	 *
72  	 * If the object was already written, this method does nothing and returns
73  	 * quickly. This case occurs whenever an object was written out of order in
74  	 * order to ensure the delta base occurred before the object that needs it.
75  	 *
76  	 * @param otp
77  	 *            the object to write.
78  	 * @throws java.io.IOException
79  	 *             the object cannot be read from the object reader, or the
80  	 *             output stream is no longer accepting output. Caller must
81  	 *             examine the type of exception and possibly its message to
82  	 *             distinguish between these cases.
83  	 */
84  	public final void writeObject(ObjectToPack otp) throws IOException {
85  		packWriter.writeObject(this, otp);
86  	}
87  
88  	/**
89  	 * Commits the object header onto the stream.
90  	 * <p>
91  	 * Once the header has been written, the object representation must be fully
92  	 * output, or packing must abort abnormally.
93  	 *
94  	 * @param otp
95  	 *            the object to pack. Header information is obtained.
96  	 * @param rawLength
97  	 *            number of bytes of the inflated content. For an object that is
98  	 *            in whole object format, this is the same as the object size.
99  	 *            For an object that is in a delta format, this is the size of
100 	 *            the inflated delta instruction stream.
101 	 * @throws java.io.IOException
102 	 *             the underlying stream refused to accept the header.
103 	 */
104 	@SuppressWarnings("ShortCircuitBoolean")
105 	public final void writeHeader(ObjectToPack otp, long rawLength)
106 			throws IOException {
107 		ObjectToPack b = otp.getDeltaBase();
108 		if (b != null && (b.isWritten() & ofsDelta)) { // Non-short-circuit logic is intentional
109 			int n = objectHeader(rawLength, OBJ_OFS_DELTA, headerBuffer);
110 			n = ofsDelta(length() - b.getOffset(), headerBuffer, n);
111 			write(headerBuffer, 0, n);
112 		} else if (otp.isDeltaRepresentation()) {
113 			int n = objectHeader(rawLength, OBJ_REF_DELTA, headerBuffer);
114 			otp.getDeltaBaseId().copyRawTo(headerBuffer, n);
115 			write(headerBuffer, 0, n + 20);
116 		} else {
117 			int n = objectHeader(rawLength, otp.getType(), headerBuffer);
118 			write(headerBuffer, 0, n);
119 		}
120 	}
121 
122 	private static final int objectHeader(long len, int type, byte[] buf) {
123 		byte b = (byte) ((type << 4) | (len & 0x0F));
124 		int n = 0;
125 		for (len >>>= 4; len != 0; len >>>= 7) {
126 			buf[n++] = (byte) (0x80 | b);
127 			b = (byte) (len & 0x7F);
128 		}
129 		buf[n++] = b;
130 		return n;
131 	}
132 
133 	private static final int ofsDelta(long diff, byte[] buf, int p) {
134 		p += ofsDeltaVarIntLength(diff);
135 		int n = p;
136 		buf[--n] = (byte) (diff & 0x7F);
137 		while ((diff >>>= 7) != 0)
138 			buf[--n] = (byte) (0x80 | (--diff & 0x7F));
139 		return p;
140 	}
141 
142 	private static final int ofsDeltaVarIntLength(long v) {
143 		int n = 1;
144 		for (; (v >>>= 7) != 0; n++)
145 			--v;
146 		return n;
147 	}
148 
149 	/**
150 	 * Get a temporary buffer writers can use to copy data with.
151 	 *
152 	 * @return a temporary buffer writers can use to copy data with.
153 	 */
154 	public final byte[] getCopyBuffer() {
155 		return copyBuffer;
156 	}
157 
158 	void endObject() {
159 		getWriteMonitor().update(1);
160 	}
161 }