View Javadoc
1   /*
2    * Copyright (C) 2017, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.internal.storage.reftable;
45  
46  import static java.nio.charset.StandardCharsets.UTF_8;
47  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.FILE_HEADER_LEN;
48  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.INDEX_BLOCK_TYPE;
49  import static org.eclipse.jgit.internal.storage.reftable.ReftableConstants.LOG_BLOCK_TYPE;
50  import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
51  
52  import java.io.IOException;
53  import java.io.OutputStream;
54  import java.util.Arrays;
55  import java.util.zip.Deflater;
56  import java.util.zip.DeflaterOutputStream;
57  
58  import org.eclipse.jgit.internal.JGitText;
59  import org.eclipse.jgit.lib.ObjectId;
60  import org.eclipse.jgit.util.NB;
61  import org.eclipse.jgit.util.io.CountingOutputStream;
62  
63  /**
64   * Wrapper to assist formatting a reftable to an {@link OutputStream}.
65   * <p>
66   * Internally buffers at block size boundaries, flushing only complete blocks to
67   * the {@code OutputStream}.
68   */
69  class ReftableOutputStream extends OutputStream {
70  	private final byte[] tmp = new byte[10];
71  	private final CountingOutputStream out;
72  	private final boolean alignBlocks;
73  
74  	private Deflater deflater;
75  	private DeflaterOutputStream compressor;
76  
77  	private int blockType;
78  	private int blockSize;
79  	private int blockStart;
80  	private byte[] blockBuf;
81  	private int cur;
82  	private long paddingUsed;
83  
84  	ReftableOutputStream(OutputStream os, int bs, boolean align) {
85  		blockSize = bs;
86  		blockBuf = new byte[bs];
87  		alignBlocks = align;
88  		out = new CountingOutputStream(os);
89  	}
90  
91  	void setBlockSize(int bs) {
92  		blockSize = bs;
93  	}
94  
95  	@Override
96  	public void write(int b) {
97  		ensureBytesAvailableInBlockBuf(1);
98  		blockBuf[cur++] = (byte) b;
99  	}
100 
101 	@Override
102 	public void write(byte[] b, int off, int cnt) {
103 		ensureBytesAvailableInBlockBuf(cnt);
104 		System.arraycopy(b, off, blockBuf, cur, cnt);
105 		cur += cnt;
106 	}
107 
108 	int bytesWrittenInBlock() {
109 		return cur;
110 	}
111 
112 	int bytesAvailableInBlock() {
113 		return blockSize - cur;
114 	}
115 
116 	long paddingUsed() {
117 		return paddingUsed;
118 	}
119 
120 	/** @return bytes flushed; excludes {@link #bytesWrittenInBlock()}. */
121 	long size() {
122 		return out.getCount();
123 	}
124 
125 	static int computeVarintSize(long val) {
126 		int n = 1;
127 		for (; (val >>>= 7) != 0; n++) {
128 			val--;
129 		}
130 		return n;
131 	}
132 
133 	void writeVarint(long val) {
134 		int n = tmp.length;
135 		tmp[--n] = (byte) (val & 0x7f);
136 		while ((val >>>= 7) != 0) {
137 			tmp[--n] = (byte) (0x80 | (--val & 0x7F));
138 		}
139 		write(tmp, n, tmp.length - n);
140 	}
141 
142 	void writeInt16(int val) {
143 		ensureBytesAvailableInBlockBuf(2);
144 		NB.encodeInt16(blockBuf, cur, val);
145 		cur += 2;
146 	}
147 
148 	void writeInt24(int val) {
149 		ensureBytesAvailableInBlockBuf(3);
150 		NB.encodeInt24(blockBuf, cur, val);
151 		cur += 3;
152 	}
153 
154 	void writeId(ObjectId id) {
155 		ensureBytesAvailableInBlockBuf(OBJECT_ID_LENGTH);
156 		id.copyRawTo(blockBuf, cur);
157 		cur += OBJECT_ID_LENGTH;
158 	}
159 
160 	void writeVarintString(String s) {
161 		writeVarintString(s.getBytes(UTF_8));
162 	}
163 
164 	void writeVarintString(byte[] msg) {
165 		writeVarint(msg.length);
166 		write(msg, 0, msg.length);
167 	}
168 
169 	private void ensureBytesAvailableInBlockBuf(int cnt) {
170 		if (cur + cnt > blockBuf.length) {
171 			int n = Math.max(cur + cnt, blockBuf.length * 2);
172 			blockBuf = Arrays.copyOf(blockBuf, n);
173 		}
174 	}
175 
176 	void flushFileHeader() throws IOException {
177 		if (cur == FILE_HEADER_LEN && out.getCount() == 0) {
178 			out.write(blockBuf, 0, cur);
179 			cur = 0;
180 		}
181 	}
182 
183 	void beginBlock(byte type) {
184 		blockType = type;
185 		blockStart = cur;
186 		cur += 4; // reserve space for 4-byte block header.
187 	}
188 
189 	void flushBlock() throws IOException {
190 		if (cur > blockSize && blockType != INDEX_BLOCK_TYPE) {
191 			throw new IOException(JGitText.get().overflowedReftableBlock);
192 		}
193 		NB.encodeInt32(blockBuf, blockStart, (blockType << 24) | cur);
194 
195 		if (blockType == LOG_BLOCK_TYPE) {
196 			// Log blocks are deflated after the block header.
197 			out.write(blockBuf, 0, 4);
198 			if (deflater != null) {
199 				deflater.reset();
200 			} else {
201 				deflater = new Deflater(Deflater.BEST_COMPRESSION);
202 				compressor = new DeflaterOutputStream(out, deflater);
203 			}
204 			compressor.write(blockBuf, 4, cur - 4);
205 			compressor.finish();
206 		} else {
207 			// Other blocks are uncompressed.
208 			out.write(blockBuf, 0, cur);
209 		}
210 
211 		cur = 0;
212 		blockType = 0;
213 		blockStart = 0;
214 	}
215 
216 	void padBetweenBlocksToNextBlock() throws IOException {
217 		if (alignBlocks) {
218 			long m = size() % blockSize;
219 			if (m > 0) {
220 				int pad = blockSize - (int) m;
221 				ensureBytesAvailableInBlockBuf(pad);
222 				Arrays.fill(blockBuf, 0, pad, (byte) 0);
223 				out.write(blockBuf, 0, pad);
224 				paddingUsed += pad;
225 			}
226 		}
227 	}
228 
229 	int estimatePadBetweenBlocks(int currentBlockSize) {
230 		if (alignBlocks) {
231 			long m = (size() + currentBlockSize) % blockSize;
232 			return m > 0 ? blockSize - (int) m : 0;
233 		}
234 		return 0;
235 	}
236 
237 	void finishFile() throws IOException {
238 		// File footer doesn't need patching for the block start.
239 		// Just flush what has been buffered.
240 		out.write(blockBuf, 0, cur);
241 		cur = 0;
242 
243 		if (deflater != null) {
244 			deflater.end();
245 		}
246 	}
247 }