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(int limit) {
95 this(limit, limit);
96 }
97
98
99
100
101
102
103
104
105
106
107
108
109 protected TemporaryBuffer(int estimatedSize, 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(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(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(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 (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 (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(OutputStream os, ProgressMonitor pm)
292 throws IOException {
293 if (pm == null)
294 pm = NullProgressMonitor.INSTANCE;
295 for (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
320
321
322
323
324
325
326 public InputStream openInputStreamWithAutoDestroy() throws IOException {
327 return new BlockInputStream() {
328 @Override
329 public void close() throws IOException {
330 super.close();
331 destroy();
332 }
333 };
334 }
335
336
337
338
339 public void reset() {
340 if (overflow != null) {
341 destroy();
342 }
343 if (blocks != null)
344 blocks.clear();
345 else
346 blocks = new ArrayList<>(initialBlocks);
347 blocks.add(new Block(Math.min(inCoreLimit, Block.SZ)));
348 }
349
350
351
352
353
354
355
356
357
358 protected abstract OutputStream overflow() throws IOException;
359
360 private Block last() {
361 return blocks.get(blocks.size() - 1);
362 }
363
364 private boolean reachedInCoreLimit() throws IOException {
365 if (inCoreLength() < inCoreLimit)
366 return false;
367
368 switchToOverflow();
369 return true;
370 }
371
372 private void switchToOverflow() throws IOException {
373 overflow = overflow();
374
375 final Block last = blocks.remove(blocks.size() - 1);
376 for (Block b : blocks)
377 overflow.write(b.buffer, 0, b.count);
378 blocks = null;
379
380 overflow = new BufferedOutputStream(overflow, Block.SZ);
381 overflow.write(last.buffer, 0, last.count);
382 }
383
384
385 @Override
386 public void close() throws IOException {
387 if (overflow != null) {
388 try {
389 overflow.close();
390 } finally {
391 overflow = null;
392 }
393 }
394 }
395
396
397
398
399 public void destroy() {
400 blocks = null;
401
402 if (overflow != null) {
403 try {
404 overflow.close();
405 } catch (IOException err) {
406
407 } finally {
408 overflow = null;
409 }
410 }
411 }
412
413
414
415
416
417
418
419
420
421
422
423 public static class LocalFile extends TemporaryBuffer {
424
425 private final File directory;
426
427
428
429
430
431
432
433
434 private File onDiskFile;
435
436
437
438
439
440
441
442
443
444
445 public LocalFile(File directory) {
446 this(directory, DEFAULT_IN_CORE_LIMIT);
447 }
448
449
450
451
452
453
454
455
456
457
458
459
460
461 public LocalFile(File directory, int inCoreLimit) {
462 super(inCoreLimit);
463 this.directory = directory;
464 }
465
466 @Override
467 protected OutputStream overflow() throws IOException {
468 onDiskFile = File.createTempFile("jgit_", ".buf", directory);
469 return new BufferedOutputStream(new FileOutputStream(onDiskFile));
470 }
471
472 @Override
473 public long length() {
474 if (onDiskFile == null) {
475 return super.length();
476 }
477 return onDiskFile.length();
478 }
479
480 @Override
481 public byte[] toByteArray() throws IOException {
482 if (onDiskFile == null) {
483 return super.toByteArray();
484 }
485
486 final long len = length();
487 if (Integer.MAX_VALUE < len)
488 throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize);
489 final byte[] out = new byte[(int) len];
490 try (FileInputStream in = new FileInputStream(onDiskFile)) {
491 IO.readFully(in, out, 0, (int) len);
492 }
493 return out;
494 }
495
496 @Override
497 public void writeTo(OutputStream os, ProgressMonitor pm)
498 throws IOException {
499 if (onDiskFile == null) {
500 super.writeTo(os, pm);
501 return;
502 }
503 if (pm == null)
504 pm = NullProgressMonitor.INSTANCE;
505 try (FileInputStream in = new FileInputStream(onDiskFile)) {
506 int cnt;
507 final byte[] buf = new byte[Block.SZ];
508 while ((cnt = in.read(buf)) >= 0) {
509 os.write(buf, 0, cnt);
510 pm.update(cnt / 1024);
511 }
512 }
513 }
514
515 @Override
516 public InputStream openInputStream() throws IOException {
517 if (onDiskFile == null)
518 return super.openInputStream();
519 return new FileInputStream(onDiskFile);
520 }
521
522 @Override
523 public InputStream openInputStreamWithAutoDestroy() throws IOException {
524 if (onDiskFile == null) {
525 return super.openInputStreamWithAutoDestroy();
526 }
527 return new FileInputStream(onDiskFile) {
528 @Override
529 public void close() throws IOException {
530 super.close();
531 destroy();
532 }
533 };
534 }
535
536 @Override
537 public void destroy() {
538 super.destroy();
539
540 if (onDiskFile != null) {
541 try {
542 if (!onDiskFile.delete())
543 onDiskFile.deleteOnExit();
544 } finally {
545 onDiskFile = null;
546 }
547 }
548 }
549 }
550
551
552
553
554
555
556
557 public static class Heap extends TemporaryBuffer {
558
559
560
561
562
563
564
565
566 public Heap(int limit) {
567 super(limit);
568 }
569
570
571
572
573
574
575
576
577
578
579
580
581
582 public Heap(int estimatedSize, int limit) {
583 super(estimatedSize, limit);
584 }
585
586 @Override
587 protected OutputStream overflow() throws IOException {
588 throw new IOException(JGitText.get().inMemoryBufferLimitExceeded);
589 }
590 }
591
592 static class Block {
593 static final int SZ = 8 * 1024;
594
595 final byte[] buffer;
596
597 int count;
598
599 Block() {
600 buffer = new byte[SZ];
601 }
602
603 Block(int sz) {
604 buffer = new byte[sz];
605 }
606
607 boolean isFull() {
608 return count == buffer.length;
609 }
610 }
611
612 private class BlockInputStream extends InputStream {
613 private byte[] singleByteBuffer;
614 private int blockIndex;
615 private Block block;
616 private int blockPos;
617
618 BlockInputStream() {
619 block = blocks.get(blockIndex);
620 }
621
622 @Override
623 public int read() throws IOException {
624 if (singleByteBuffer == null)
625 singleByteBuffer = new byte[1];
626 int n = read(singleByteBuffer);
627 return n == 1 ? singleByteBuffer[0] & 0xff : -1;
628 }
629
630 @Override
631 public long skip(long cnt) throws IOException {
632 long skipped = 0;
633 while (0 < cnt) {
634 int n = (int) Math.min(block.count - blockPos, cnt);
635 if (0 < n) {
636 blockPos += n;
637 skipped += n;
638 cnt -= n;
639 } else if (nextBlock())
640 continue;
641 else
642 break;
643 }
644 return skipped;
645 }
646
647 @Override
648 public int read(byte[] b, int off, int len) throws IOException {
649 if (len == 0)
650 return 0;
651 int copied = 0;
652 while (0 < len) {
653 int c = Math.min(block.count - blockPos, len);
654 if (0 < c) {
655 System.arraycopy(block.buffer, blockPos, b, off, c);
656 blockPos += c;
657 off += c;
658 len -= c;
659 copied += c;
660 } else if (nextBlock())
661 continue;
662 else
663 break;
664 }
665 return 0 < copied ? copied : -1;
666 }
667
668 private boolean nextBlock() {
669 if (++blockIndex < blocks.size()) {
670 block = blocks.get(blockIndex);
671 blockPos = 0;
672 return true;
673 }
674 return false;
675 }
676 }
677 }