1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
65
66
67
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
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;
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
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
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
239
240 out.write(blockBuf, 0, cur);
241 cur = 0;
242
243 if (deflater != null) {
244 deflater.end();
245 }
246 }
247 }