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  import java.security.MessageDigest;
21  
22  import org.eclipse.jgit.internal.JGitText;
23  import org.eclipse.jgit.lib.Constants;
24  import org.eclipse.jgit.lib.ProgressMonitor;
25  import org.eclipse.jgit.util.NB;
26  
27  /**
28   * Custom output stream to support
29   * {@link org.eclipse.jgit.internal.storage.pack.PackWriter}.
30   */
31  public final class PackOutputStream extends OutputStream {
32  	private static final int BYTES_TO_WRITE_BEFORE_CANCEL_CHECK = 128 * 1024;
33  
34  	private final ProgressMonitor writeMonitor;
35  
36  	private final OutputStream out;
37  
38  	private final PackWriter packWriter;
39  
40  	private final MessageDigest md = Constants.newMessageDigest();
41  
42  	private long count;
43  
44  	private final byte[] headerBuffer = new byte[32];
45  
46  	private final byte[] copyBuffer = new byte[64 << 10];
47  
48  	private long checkCancelAt;
49  
50  	private boolean ofsDelta;
51  
52  	/**
53  	 * Initialize a pack output stream.
54  	 * <p>
55  	 * This constructor is exposed to support debugging the JGit library only.
56  	 * Application or storage level code should not create a PackOutputStream,
57  	 * instead use {@link org.eclipse.jgit.internal.storage.pack.PackWriter},
58  	 * and let the writer create the stream.
59  	 *
60  	 * @param writeMonitor
61  	 *            monitor to update on object output progress.
62  	 * @param out
63  	 *            target stream to receive all object contents.
64  	 * @param pw
65  	 *            packer that is going to perform the output.
66  	 */
67  	public PackOutputStream(final ProgressMonitor writeMonitor,
68  			final OutputStream out, final PackWriter pw) {
69  		this.writeMonitor = writeMonitor;
70  		this.out = out;
71  		this.packWriter = pw;
72  		this.checkCancelAt = BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
73  	}
74  
75  	/** {@inheritDoc} */
76  	@Override
77  	public final void write(int b) throws IOException {
78  		count++;
79  		out.write(b);
80  		md.update((byte) b);
81  	}
82  
83  	/** {@inheritDoc} */
84  	@Override
85  	public final void write(byte[] b, int off, int len)
86  			throws IOException {
87  		while (0 < len) {
88  			final int n = Math.min(len, BYTES_TO_WRITE_BEFORE_CANCEL_CHECK);
89  			count += n;
90  
91  			if (checkCancelAt <= count) {
92  				if (writeMonitor.isCancelled()) {
93  					throw new IOException(
94  							JGitText.get().packingCancelledDuringObjectsWriting);
95  				}
96  				checkCancelAt = count + BYTES_TO_WRITE_BEFORE_CANCEL_CHECK;
97  			}
98  
99  			out.write(b, off, n);
100 			md.update(b, off, n);
101 
102 			off += n;
103 			len -= n;
104 		}
105 	}
106 
107 	/** {@inheritDoc} */
108 	@Override
109 	public void flush() throws IOException {
110 		out.flush();
111 	}
112 
113 	final void writeFileHeader(int version, long objectCount)
114 			throws IOException {
115 		System.arraycopy(PACK_SIGNATURE, 0, headerBuffer, 0, 4);
116 		NB.encodeInt32(headerBuffer, 4, version);
117 		NB.encodeInt32(headerBuffer, 8, (int) objectCount);
118 		write(headerBuffer, 0, 12);
119 		ofsDelta = packWriter.isDeltaBaseAsOffset();
120 	}
121 
122 	/**
123 	 * Write one object.
124 	 *
125 	 * If the object was already written, this method does nothing and returns
126 	 * quickly. This case occurs whenever an object was written out of order in
127 	 * order to ensure the delta base occurred before the object that needs it.
128 	 *
129 	 * @param otp
130 	 *            the object to write.
131 	 * @throws java.io.IOException
132 	 *             the object cannot be read from the object reader, or the
133 	 *             output stream is no longer accepting output. Caller must
134 	 *             examine the type of exception and possibly its message to
135 	 *             distinguish between these cases.
136 	 */
137 	public final void writeObject(ObjectToPack otp) throws IOException {
138 		packWriter.writeObject(this, otp);
139 	}
140 
141 	/**
142 	 * Commits the object header onto the stream.
143 	 * <p>
144 	 * Once the header has been written, the object representation must be fully
145 	 * output, or packing must abort abnormally.
146 	 *
147 	 * @param otp
148 	 *            the object to pack. Header information is obtained.
149 	 * @param rawLength
150 	 *            number of bytes of the inflated content. For an object that is
151 	 *            in whole object format, this is the same as the object size.
152 	 *            For an object that is in a delta format, this is the size of
153 	 *            the inflated delta instruction stream.
154 	 * @throws java.io.IOException
155 	 *             the underlying stream refused to accept the header.
156 	 */
157 	@SuppressWarnings("ShortCircuitBoolean")
158 	public final void writeHeader(ObjectToPack otp, long rawLength)
159 			throws IOException {
160 		ObjectToPack b = otp.getDeltaBase();
161 		if (b != null && (b.isWritten() & ofsDelta)) { // Non-short-circuit logic is intentional
162 			int n = objectHeader(rawLength, OBJ_OFS_DELTA, headerBuffer);
163 			n = ofsDelta(count - b.getOffset(), headerBuffer, n);
164 			write(headerBuffer, 0, n);
165 		} else if (otp.isDeltaRepresentation()) {
166 			int n = objectHeader(rawLength, OBJ_REF_DELTA, headerBuffer);
167 			otp.getDeltaBaseId().copyRawTo(headerBuffer, n);
168 			write(headerBuffer, 0, n + 20);
169 		} else {
170 			int n = objectHeader(rawLength, otp.getType(), headerBuffer);
171 			write(headerBuffer, 0, n);
172 		}
173 	}
174 
175 	private static final int objectHeader(long len, int type, byte[] buf) {
176 		byte b = (byte) ((type << 4) | (len & 0x0F));
177 		int n = 0;
178 		for (len >>>= 4; len != 0; len >>>= 7) {
179 			buf[n++] = (byte) (0x80 | b);
180 			b = (byte) (len & 0x7F);
181 		}
182 		buf[n++] = b;
183 		return n;
184 	}
185 
186 	private static final int ofsDelta(long diff, byte[] buf, int p) {
187 		p += ofsDeltaVarIntLength(diff);
188 		int n = p;
189 		buf[--n] = (byte) (diff & 0x7F);
190 		while ((diff >>>= 7) != 0)
191 			buf[--n] = (byte) (0x80 | (--diff & 0x7F));
192 		return p;
193 	}
194 
195 	private static final int ofsDeltaVarIntLength(long v) {
196 		int n = 1;
197 		for (; (v >>>= 7) != 0; n++)
198 			--v;
199 		return n;
200 	}
201 
202 	/**
203 	 * Get a temporary buffer writers can use to copy data with.
204 	 *
205 	 * @return a temporary buffer writers can use to copy data with.
206 	 */
207 	public final byte[] getCopyBuffer() {
208 		return copyBuffer;
209 	}
210 
211 	void endObject() {
212 		writeMonitor.update(1);
213 	}
214 
215 	/**
216 	 * Get total number of bytes written since stream start.
217 	 *
218 	 * @return total number of bytes written since stream start.
219 	 */
220 	public final long length() {
221 		return count;
222 	}
223 
224 	/** @return obtain the current SHA-1 digest. */
225 	final byte[] getDigest() {
226 		return md.digest();
227 	}
228 }