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 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
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
96 @Override
97 public void write(int b) {
98 ensureBytesAvailableInBlockBuf(1);
99 blockBuf[cur++] = (byte) b;
100 }
101
102
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
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;
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
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
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
241
242 out.write(blockBuf, 0, cur);
243 cur = 0;
244
245 if (deflater != null) {
246 deflater.end();
247 }
248 }
249 }