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
45 package org.eclipse.jgit.util;
46
47 import java.io.BufferedOutputStream;
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.OutputStream;
54 import java.util.ArrayList;
55
56 import org.eclipse.jgit.internal.JGitText;
57 import org.eclipse.jgit.lib.NullProgressMonitor;
58 import org.eclipse.jgit.lib.ProgressMonitor;
59
60
61
62
63
64
65
66 public abstract class TemporaryBuffer extends OutputStream {
67
68 protected static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024;
69
70
71 ArrayList<Block> blocks;
72
73
74
75
76
77
78
79 private int inCoreLimit;
80
81
82 private int initialBlocks;
83
84
85 private OutputStream overflow;
86
87
88
89
90
91
92
93
94 protected TemporaryBuffer(final int limit) {
95 this(limit, limit);
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109 protected TemporaryBuffer(final int estimatedSize, final int limit) {
110 if (estimatedSize > limit)
111 throw new IllegalArgumentException();
112 this.inCoreLimit = limit;
113 this.initialBlocks = (estimatedSize - 1) / Block.SZ + 1;
114 reset();
115 }
116
117
118 @Override
119 public void write(final int b) throws IOException {
120 if (overflow != null) {
121 overflow.write(b);
122 return;
123 }
124
125 Block s = last();
126 if (s.isFull()) {
127 if (reachedInCoreLimit()) {
128 overflow.write(b);
129 return;
130 }
131
132 s = new Block();
133 blocks.add(s);
134 }
135 s.buffer[s.count++] = (byte) b;
136 }
137
138
139 @Override
140 public void write(final byte[] b, int off, int len) throws IOException {
141 if (overflow == null) {
142 while (len > 0) {
143 Block s = last();
144 if (s.isFull()) {
145 if (reachedInCoreLimit())
146 break;
147
148 s = new Block();
149 blocks.add(s);
150 }
151
152 final int n = Math.min(s.buffer.length - s.count, len);
153 System.arraycopy(b, off, s.buffer, s.count, n);
154 s.count += n;
155 len -= n;
156 off += n;
157 }
158 }
159
160 if (len > 0)
161 overflow.write(b, off, len);
162 }
163
164
165
166
167
168
169
170
171 protected void doFlush() throws IOException {
172 if (overflow == null)
173 switchToOverflow();
174 overflow.flush();
175 }
176
177
178
179
180
181
182
183
184
185
186 public void copy(final InputStream in) throws IOException {
187 if (blocks != null) {
188 for (;;) {
189 Block s = last();
190 if (s.isFull()) {
191 if (reachedInCoreLimit())
192 break;
193 s = new Block();
194 blocks.add(s);
195 }
196
197 int n = in.read(s.buffer, s.count, s.buffer.length - s.count);
198 if (n < 1)
199 return;
200 s.count += n;
201 }
202 }
203
204 final byte[] tmp = new byte[Block.SZ];
205 int n;
206 while ((n = in.read(tmp)) > 0)
207 overflow.write(tmp, 0, n);
208 }
209
210
211
212
213
214
215
216
217 public long length() {
218 return inCoreLength();
219 }
220
221 private long inCoreLength() {
222 final Block last = last();
223 return ((long) blocks.size() - 1) * Block.SZ + last.count;
224 }
225
226
227
228
229
230
231
232
233
234
235 public byte[] toByteArray() throws IOException {
236 final long len = length();
237 if (Integer.MAX_VALUE < len)
238 throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize);
239 final byte[] out = new byte[(int) len];
240 int outPtr = 0;
241 for (final Block b : blocks) {
242 System.arraycopy(b.buffer, 0, out, outPtr, b.count);
243 outPtr += b.count;
244 }
245 return out;
246 }
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261 public byte[] toByteArray(int limit) throws IOException {
262 final long len = Math.min(length(), limit);
263 if (Integer.MAX_VALUE < len)
264 throw new OutOfMemoryError(
265 JGitText.get().lengthExceedsMaximumArraySize);
266 final byte[] out = new byte[(int) len];
267 int outPtr = 0;
268 for (final Block b : blocks) {
269 System.arraycopy(b.buffer, 0, out, outPtr, b.count);
270 outPtr += b.count;
271 }
272 return out;
273 }
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291 public void writeTo(final OutputStream os, ProgressMonitor pm)
292 throws IOException {
293 if (pm == null)
294 pm = NullProgressMonitor.INSTANCE;
295 for (final Block b : blocks) {
296 os.write(b.buffer, 0, b.count);
297 pm.update(b.count / 1024);
298 }
299 }
300
301
302
303
304
305
306
307
308
309
310
311
312 public InputStream openInputStream() throws IOException {
313 return new BlockInputStream();
314 }
315
316
317
318
319 public void reset() {
320 if (overflow != null) {
321 destroy();
322 }
323 if (blocks != null)
324 blocks.clear();
325 else
326 blocks = new ArrayList<>(initialBlocks);
327 blocks.add(new Block(Math.min(inCoreLimit, Block.SZ)));
328 }
329
330
331
332
333
334
335
336
337
338 protected abstract OutputStream overflow() throws IOException;
339
340 private Block last() {
341 return blocks.get(blocks.size() - 1);
342 }
343
344 private boolean reachedInCoreLimit() throws IOException {
345 if (inCoreLength() < inCoreLimit)
346 return false;
347
348 switchToOverflow();
349 return true;
350 }
351
352 private void switchToOverflow() throws IOException {
353 overflow = overflow();
354
355 final Block last = blocks.remove(blocks.size() - 1);
356 for (final Block b : blocks)
357 overflow.write(b.buffer, 0, b.count);
358 blocks = null;
359
360 overflow = new BufferedOutputStream(overflow, Block.SZ);
361 overflow.write(last.buffer, 0, last.count);
362 }
363
364
365 @Override
366 public void close() throws IOException {
367 if (overflow != null) {
368 try {
369 overflow.close();
370 } finally {
371 overflow = null;
372 }
373 }
374 }
375
376
377
378
379 public void destroy() {
380 blocks = null;
381
382 if (overflow != null) {
383 try {
384 overflow.close();
385 } catch (IOException err) {
386
387 } finally {
388 overflow = null;
389 }
390 }
391 }
392
393
394
395
396
397
398
399
400
401
402
403 public static class LocalFile extends TemporaryBuffer {
404
405 private final File directory;
406
407
408
409
410
411
412
413
414 private File onDiskFile;
415
416
417
418
419
420
421
422
423
424
425 public LocalFile(final File directory) {
426 this(directory, DEFAULT_IN_CORE_LIMIT);
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441 public LocalFile(final File directory, final int inCoreLimit) {
442 super(inCoreLimit);
443 this.directory = directory;
444 }
445
446 @Override
447 protected OutputStream overflow() throws IOException {
448 onDiskFile = File.createTempFile("jgit_", ".buf", directory);
449 return new BufferedOutputStream(new FileOutputStream(onDiskFile));
450 }
451
452 @Override
453 public long length() {
454 if (onDiskFile == null) {
455 return super.length();
456 }
457 return onDiskFile.length();
458 }
459
460 @Override
461 public byte[] toByteArray() throws IOException {
462 if (onDiskFile == null) {
463 return super.toByteArray();
464 }
465
466 final long len = length();
467 if (Integer.MAX_VALUE < len)
468 throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize);
469 final byte[] out = new byte[(int) len];
470 final FileInputStream in = new FileInputStream(onDiskFile);
471 try {
472 IO.readFully(in, out, 0, (int) len);
473 } finally {
474 in.close();
475 }
476 return out;
477 }
478
479 @Override
480 public void writeTo(final OutputStream os, ProgressMonitor pm)
481 throws IOException {
482 if (onDiskFile == null) {
483 super.writeTo(os, pm);
484 return;
485 }
486 if (pm == null)
487 pm = NullProgressMonitor.INSTANCE;
488 final FileInputStream in = new FileInputStream(onDiskFile);
489 try {
490 int cnt;
491 final byte[] buf = new byte[Block.SZ];
492 while ((cnt = in.read(buf)) >= 0) {
493 os.write(buf, 0, cnt);
494 pm.update(cnt / 1024);
495 }
496 } finally {
497 in.close();
498 }
499 }
500
501 @Override
502 public InputStream openInputStream() throws IOException {
503 if (onDiskFile == null)
504 return super.openInputStream();
505 return new FileInputStream(onDiskFile);
506 }
507
508 @Override
509 public void destroy() {
510 super.destroy();
511
512 if (onDiskFile != null) {
513 try {
514 if (!onDiskFile.delete())
515 onDiskFile.deleteOnExit();
516 } finally {
517 onDiskFile = null;
518 }
519 }
520 }
521 }
522
523
524
525
526
527
528
529 public static class Heap extends TemporaryBuffer {
530
531
532
533
534
535
536
537
538 public Heap(final int limit) {
539 super(limit);
540 }
541
542
543
544
545
546
547
548
549
550
551
552
553
554 public Heap(final int estimatedSize, final int limit) {
555 super(estimatedSize, limit);
556 }
557
558 @Override
559 protected OutputStream overflow() throws IOException {
560 throw new IOException(JGitText.get().inMemoryBufferLimitExceeded);
561 }
562 }
563
564 static class Block {
565 static final int SZ = 8 * 1024;
566
567 final byte[] buffer;
568
569 int count;
570
571 Block() {
572 buffer = new byte[SZ];
573 }
574
575 Block(int sz) {
576 buffer = new byte[sz];
577 }
578
579 boolean isFull() {
580 return count == buffer.length;
581 }
582 }
583
584 private class BlockInputStream extends InputStream {
585 private byte[] singleByteBuffer;
586 private int blockIndex;
587 private Block block;
588 private int blockPos;
589
590 BlockInputStream() {
591 block = blocks.get(blockIndex);
592 }
593
594 @Override
595 public int read() throws IOException {
596 if (singleByteBuffer == null)
597 singleByteBuffer = new byte[1];
598 int n = read(singleByteBuffer);
599 return n == 1 ? singleByteBuffer[0] & 0xff : -1;
600 }
601
602 @Override
603 public long skip(long cnt) throws IOException {
604 long skipped = 0;
605 while (0 < cnt) {
606 int n = (int) Math.min(block.count - blockPos, cnt);
607 if (0 < n) {
608 blockPos += n;
609 skipped += n;
610 cnt -= n;
611 } else if (nextBlock())
612 continue;
613 else
614 break;
615 }
616 return skipped;
617 }
618
619 @Override
620 public int read(byte[] b, int off, int len) throws IOException {
621 if (len == 0)
622 return 0;
623 int copied = 0;
624 while (0 < len) {
625 int c = Math.min(block.count - blockPos, len);
626 if (0 < c) {
627 System.arraycopy(block.buffer, blockPos, b, off, c);
628 blockPos += c;
629 off += c;
630 len -= c;
631 copied += c;
632 } else if (nextBlock())
633 continue;
634 else
635 break;
636 }
637 return 0 < copied ? copied : -1;
638 }
639
640 private boolean nextBlock() {
641 if (++blockIndex < blocks.size()) {
642 block = blocks.get(blockIndex);
643 blockPos = 0;
644 return true;
645 }
646 return false;
647 }
648 }
649 }