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 org.eclipse.jgit.lib.Constants.CHARSET;
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  	/** {@inheritDoc} */
96  	@Override
97  	public void write(int b) {
98  		ensureBytesAvailableInBlockBuf(1);
99  		blockBuf[cur++] = (byte) b;
100 	}
101 
102 	/** {@inheritDoc} */
103 	@Override
104 	public void write(byte[] b, int off, int cnt) {
105 		ensureBytesAvailableInBlockBuf(cnt);
106 		System.arraycopy(b, off, blockBuf, cur, cnt);
107 		cur += cnt;
108 	}
109 
110 	int bytesWrittenInBlock() {
111 		return cur;
112 	}
113 
114 	int bytesAvailableInBlock() {
115 		return blockSize - cur;
116 	}
117 
118 	long paddingUsed() {
119 		return paddingUsed;
120 	}
121 
122 	/** @return bytes flushed; excludes {@link #bytesWrittenInBlock()}. */
123 	long size() {
124 		return out.getCount();
125 	}
126 
127 	static int computeVarintSize(long val) {
128 		int n = 1;
129 		for (; (val >>>= 7) != 0; n++) {
130 			val--;
131 		}
132 		return n;
133 	}
134 
135 	void writeVarint(long val) {
136 		int n = tmp.length;
137 		tmp[--n] = (byte) (val & 0x7f);
138 		while ((val >>>= 7) != 0) {
139 			tmp[--n] = (byte) (0x80 | (--val & 0x7F));
140 		}
141 		write(tmp, n, tmp.length - n);
142 	}
143 
144 	void writeInt16(int val) {
145 		ensureBytesAvailableInBlockBuf(2);
146 		NB.encodeInt16(blockBuf, cur, val);
147 		cur += 2;
148 	}
149 
150 	void writeInt24(int val) {
151 		ensureBytesAvailableInBlockBuf(3);
152 		NB.encodeInt24(blockBuf, cur, val);
153 		cur += 3;
154 	}
155 
156 	void writeId(ObjectId id) {
157 		ensureBytesAvailableInBlockBuf(OBJECT_ID_LENGTH);
158 		id.copyRawTo(blockBuf, cur);
159 		cur += OBJECT_ID_LENGTH;
160 	}
161 
162 	void writeVarintString(String s) {
163 		writeVarintString(s.getBytes(CHARSET));
164 	}
165 
166 	void writeVarintString(byte[] msg) {
167 		writeVarint(msg.length);
168 		write(msg, 0, msg.length);
169 	}
170 
171 	private void ensureBytesAvailableInBlockBuf(int cnt) {
172 		if (cur + cnt > blockBuf.length) {
173 			int n = Math.max(cur + cnt, blockBuf.length * 2);
174 			blockBuf = Arrays.copyOf(blockBuf, n);
175 		}
176 	}
177 
178 	void flushFileHeader() throws IOException {
179 		if (cur == FILE_HEADER_LEN && out.getCount() == 0) {
180 			out.write(blockBuf, 0, cur);
181 			cur = 0;
182 		}
183 	}
184 
185 	void beginBlock(byte type) {
186 		blockType = type;
187 		blockStart = cur;
188 		cur += 4; // reserve space for 4-byte block header.
189 	}
190 
191 	void flushBlock() throws IOException {
192 		if (cur > blockSize && blockType != INDEX_BLOCK_TYPE) {
193 			throw new IOException(JGitText.get().overflowedReftableBlock);
194 		}
195 		NB.encodeInt32(blockBuf, blockStart, (blockType << 24) | cur);
196 
197 		if (blockType == LOG_BLOCK_TYPE) {
198 			// Log blocks are deflated after the block header.
199 			out.write(blockBuf, 0, 4);
200 			if (deflater != null) {
201 				deflater.reset();
202 			} else {
203 				deflater = new Deflater(Deflater.BEST_COMPRESSION);
204 				compressor = new DeflaterOutputStream(out, deflater);
205 			}
206 			compressor.write(blockBuf, 4, cur - 4);
207 			compressor.finish();
208 		} else {
209 			// Other blocks are uncompressed.
210 			out.write(blockBuf, 0, cur);
211 		}
212 
213 		cur = 0;
214 		blockType = 0;
215 		blockStart = 0;
216 	}
217 
218 	void padBetweenBlocksToNextBlock() throws IOException {
219 		if (alignBlocks) {
220 			long m = size() % blockSize;
221 			if (m > 0) {
222 				int pad = blockSize - (int) m;
223 				ensureBytesAvailableInBlockBuf(pad);
224 				Arrays.fill(blockBuf, 0, pad, (byte) 0);
225 				out.write(blockBuf, 0, pad);
226 				paddingUsed += pad;
227 			}
228 		}
229 	}
230 
231 	int estimatePadBetweenBlocks(int currentBlockSize) {
232 		if (alignBlocks) {
233 			long m = (size() + currentBlockSize) % blockSize;
234 			return m > 0 ? blockSize - (int) m : 0;
235 		}
236 		return 0;
237 	}
238 
239 	void finishFile() throws IOException {
240 		// File footer doesn't need patching for the block start.
241 		// Just flush what has been buffered.
242 		out.write(blockBuf, 0, cur);
243 		cur = 0;
244 
245 		if (deflater != null) {
246 			deflater.end();
247 		}
248 	}
249 }