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