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 final byte[] out = new byte[(int) len];
234 int outPtr = 0;
235 for (Block b : blocks) {
236 System.arraycopy(b.buffer, 0, out, outPtr, b.count);
237 outPtr += b.count;
238 }
239 return out;
240 }
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258 public void writeTo(OutputStream os, ProgressMonitor pm)
259 throws IOException {
260 if (pm == null)
261 pm = NullProgressMonitor.INSTANCE;
262 for (Block b : blocks) {
263 os.write(b.buffer, 0, b.count);
264 pm.update(b.count / 1024);
265 }
266 }
267
268
269
270
271
272
273
274
275
276
277
278
279 public InputStream openInputStream() throws IOException {
280 return new BlockInputStream();
281 }
282
283
284
285
286
287
288
289
290
291
292
293 public InputStream openInputStreamWithAutoDestroy() throws IOException {
294 return new BlockInputStream() {
295 @Override
296 public void close() throws IOException {
297 super.close();
298 destroy();
299 }
300 };
301 }
302
303
304
305
306 public void reset() {
307 if (overflow != null) {
308 destroy();
309 }
310 if (blocks != null)
311 blocks.clear();
312 else
313 blocks = new ArrayList<>(initialBlocks);
314 blocks.add(new Block(Math.min(inCoreLimit, Block.SZ)));
315 }
316
317
318
319
320
321
322
323
324
325 protected abstract OutputStream overflow() throws IOException;
326
327 private Block last() {
328 return blocks.get(blocks.size() - 1);
329 }
330
331 private boolean reachedInCoreLimit() throws IOException {
332 if (inCoreLength() < inCoreLimit)
333 return false;
334
335 switchToOverflow();
336 return true;
337 }
338
339 private void switchToOverflow() throws IOException {
340 overflow = overflow();
341
342 final Block last = blocks.remove(blocks.size() - 1);
343 for (Block b : blocks)
344 overflow.write(b.buffer, 0, b.count);
345 blocks = null;
346
347 overflow = new BufferedOutputStream(overflow, Block.SZ);
348 overflow.write(last.buffer, 0, last.count);
349 }
350
351
352 @Override
353 public void close() throws IOException {
354 if (overflow != null) {
355 try {
356 overflow.close();
357 } finally {
358 overflow = null;
359 }
360 }
361 }
362
363
364
365
366 public void destroy() {
367 blocks = null;
368
369 if (overflow != null) {
370 try {
371 overflow.close();
372 } catch (IOException err) {
373
374 } finally {
375 overflow = null;
376 }
377 }
378 }
379
380
381
382
383
384
385
386
387
388
389
390 public static class LocalFile extends TemporaryBuffer {
391
392 private final File directory;
393
394
395
396
397
398
399
400
401 private File onDiskFile;
402
403
404
405
406
407
408
409
410
411
412 public LocalFile(File directory) {
413 this(directory, DEFAULT_IN_CORE_LIMIT);
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427
428 public LocalFile(File directory, int inCoreLimit) {
429 super(inCoreLimit);
430 this.directory = directory;
431 }
432
433 @Override
434 protected OutputStream overflow() throws IOException {
435 onDiskFile = File.createTempFile("jgit_", ".buf", directory);
436 return new BufferedOutputStream(new FileOutputStream(onDiskFile));
437 }
438
439 @Override
440 public long length() {
441 if (onDiskFile == null) {
442 return super.length();
443 }
444 return onDiskFile.length();
445 }
446
447 @Override
448 public byte[] toByteArray() throws IOException {
449 if (onDiskFile == null) {
450 return super.toByteArray();
451 }
452
453 final long len = length();
454 if (Integer.MAX_VALUE < len)
455 throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize);
456 final byte[] out = new byte[(int) len];
457 try (FileInputStream in = new FileInputStream(onDiskFile)) {
458 IO.readFully(in, out, 0, (int) len);
459 }
460 return out;
461 }
462
463 @Override
464 public void writeTo(OutputStream os, ProgressMonitor pm)
465 throws IOException {
466 if (onDiskFile == null) {
467 super.writeTo(os, pm);
468 return;
469 }
470 if (pm == null)
471 pm = NullProgressMonitor.INSTANCE;
472 try (FileInputStream in = new FileInputStream(onDiskFile)) {
473 int cnt;
474 final byte[] buf = new byte[Block.SZ];
475 while ((cnt = in.read(buf)) >= 0) {
476 os.write(buf, 0, cnt);
477 pm.update(cnt / 1024);
478 }
479 }
480 }
481
482 @Override
483 public InputStream openInputStream() throws IOException {
484 if (onDiskFile == null)
485 return super.openInputStream();
486 return new FileInputStream(onDiskFile);
487 }
488
489 @Override
490 public InputStream openInputStreamWithAutoDestroy() throws IOException {
491 if (onDiskFile == null) {
492 return super.openInputStreamWithAutoDestroy();
493 }
494 return new FileInputStream(onDiskFile) {
495 @Override
496 public void close() throws IOException {
497 super.close();
498 destroy();
499 }
500 };
501 }
502
503 @Override
504 public void destroy() {
505 super.destroy();
506
507 if (onDiskFile != null) {
508 try {
509 if (!onDiskFile.delete())
510 onDiskFile.deleteOnExit();
511 } finally {
512 onDiskFile = null;
513 }
514 }
515 }
516 }
517
518
519
520
521
522
523
524 public static class Heap extends TemporaryBuffer {
525
526
527
528
529
530
531
532
533 public Heap(int limit) {
534 super(limit);
535 }
536
537
538
539
540
541
542
543
544
545
546
547
548
549 public Heap(int estimatedSize, int limit) {
550 super(estimatedSize, limit);
551 }
552
553 @Override
554 protected OutputStream overflow() throws IOException {
555 throw new IOException(JGitText.get().inMemoryBufferLimitExceeded);
556 }
557 }
558
559 static class Block {
560 static final int SZ = 8 * 1024;
561
562 final byte[] buffer;
563
564 int count;
565
566 Block() {
567 buffer = new byte[SZ];
568 }
569
570 Block(int sz) {
571 buffer = new byte[sz];
572 }
573
574 boolean isFull() {
575 return count == buffer.length;
576 }
577 }
578
579 private class BlockInputStream extends InputStream {
580 private byte[] singleByteBuffer;
581 private int blockIndex;
582 private Block block;
583 private int blockPos;
584
585 BlockInputStream() {
586 block = blocks.get(blockIndex);
587 }
588
589 @Override
590 public int read() throws IOException {
591 if (singleByteBuffer == null)
592 singleByteBuffer = new byte[1];
593 int n = read(singleByteBuffer);
594 return n == 1 ? singleByteBuffer[0] & 0xff : -1;
595 }
596
597 @Override
598 public long skip(long cnt) throws IOException {
599 long skipped = 0;
600 while (0 < cnt) {
601 int n = (int) Math.min(block.count - blockPos, cnt);
602 if (0 < n) {
603 blockPos += n;
604 skipped += n;
605 cnt -= n;
606 } else if (nextBlock())
607 continue;
608 else
609 break;
610 }
611 return skipped;
612 }
613
614 @Override
615 public int read(byte[] b, int off, int len) throws IOException {
616 if (len == 0)
617 return 0;
618 int copied = 0;
619 while (0 < len) {
620 int c = Math.min(block.count - blockPos, len);
621 if (0 < c) {
622 System.arraycopy(block.buffer, blockPos, b, off, c);
623 blockPos += c;
624 off += c;
625 len -= c;
626 copied += c;
627 } else if (nextBlock())
628 continue;
629 else
630 break;
631 }
632 return 0 < copied ? copied : -1;
633 }
634
635 private boolean nextBlock() {
636 if (++blockIndex < blocks.size()) {
637 block = blocks.get(blockIndex);
638 blockPos = 0;
639 return true;
640 }
641 return false;
642 }
643 }
644 }