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 import org.eclipse.jgit.util.io.SafeBufferedOutputStream;
60
61
62
63
64
65
66
67 public abstract class TemporaryBuffer extends OutputStream {
68
69 protected static final int DEFAULT_IN_CORE_LIMIT = 1024 * 1024;
70
71
72 ArrayList<Block> blocks;
73
74
75
76
77
78
79
80 private int inCoreLimit;
81
82
83 private int initialBlocks;
84
85
86 private OutputStream overflow;
87
88
89
90
91
92
93
94
95 protected TemporaryBuffer(final int limit) {
96 this(limit, limit);
97 }
98
99
100
101
102
103
104
105
106
107
108
109
110 protected TemporaryBuffer(final int estimatedSize, final int limit) {
111 if (estimatedSize > limit)
112 throw new IllegalArgumentException();
113 this.inCoreLimit = limit;
114 this.initialBlocks = (estimatedSize - 1) / Block.SZ + 1;
115 reset();
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 @Override
139 public void write(final byte[] b, int off, int len) throws IOException {
140 if (overflow == null) {
141 while (len > 0) {
142 Block s = last();
143 if (s.isFull()) {
144 if (reachedInCoreLimit())
145 break;
146
147 s = new Block();
148 blocks.add(s);
149 }
150
151 final int n = Math.min(s.buffer.length - s.count, len);
152 System.arraycopy(b, off, s.buffer, s.count, n);
153 s.count += n;
154 len -= n;
155 off += n;
156 }
157 }
158
159 if (len > 0)
160 overflow.write(b, off, len);
161 }
162
163
164
165
166
167
168
169
170 protected void doFlush() throws IOException {
171 if (overflow == null)
172 switchToOverflow();
173 overflow.flush();
174 }
175
176
177
178
179
180
181
182
183
184
185 public void copy(final InputStream in) throws IOException {
186 if (blocks != null) {
187 for (;;) {
188 Block s = last();
189 if (s.isFull()) {
190 if (reachedInCoreLimit())
191 break;
192 s = new Block();
193 blocks.add(s);
194 }
195
196 int n = in.read(s.buffer, s.count, s.buffer.length - s.count);
197 if (n < 1)
198 return;
199 s.count += n;
200 }
201 }
202
203 final byte[] tmp = new byte[Block.SZ];
204 int n;
205 while ((n = in.read(tmp)) > 0)
206 overflow.write(tmp, 0, n);
207 }
208
209
210
211
212
213
214
215
216 public long length() {
217 return inCoreLength();
218 }
219
220 private long inCoreLength() {
221 final Block last = last();
222 return ((long) blocks.size() - 1) * Block.SZ + last.count;
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236 public byte[] toByteArray() throws IOException {
237 final long len = length();
238 if (Integer.MAX_VALUE < len)
239 throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize);
240 final byte[] out = new byte[(int) len];
241 int outPtr = 0;
242 for (final Block b : blocks) {
243 System.arraycopy(b.buffer, 0, out, outPtr, b.count);
244 outPtr += b.count;
245 }
246 return out;
247 }
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266 public byte[] toByteArray(int limit) throws IOException {
267 final long len = Math.min(length(), limit);
268 if (Integer.MAX_VALUE < len)
269 throw new OutOfMemoryError(
270 JGitText.get().lengthExceedsMaximumArraySize);
271 final byte[] out = new byte[(int) len];
272 int outPtr = 0;
273 for (final Block b : blocks) {
274 System.arraycopy(b.buffer, 0, out, outPtr, b.count);
275 outPtr += b.count;
276 }
277 return out;
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 public void writeTo(final OutputStream os, ProgressMonitor pm)
297 throws IOException {
298 if (pm == null)
299 pm = NullProgressMonitor.INSTANCE;
300 for (final Block b : blocks) {
301 os.write(b.buffer, 0, b.count);
302 pm.update(b.count / 1024);
303 }
304 }
305
306
307
308
309
310
311
312
313
314
315
316
317 public InputStream openInputStream() throws IOException {
318 return new BlockInputStream();
319 }
320
321
322 public void reset() {
323 if (overflow != null) {
324 destroy();
325 }
326 if (blocks != null)
327 blocks.clear();
328 else
329 blocks = new ArrayList<Block>(initialBlocks);
330 blocks.add(new Block(Math.min(inCoreLimit, Block.SZ)));
331 }
332
333
334
335
336
337
338
339
340
341 protected abstract OutputStream overflow() throws IOException;
342
343 private Block last() {
344 return blocks.get(blocks.size() - 1);
345 }
346
347 private boolean reachedInCoreLimit() throws IOException {
348 if (inCoreLength() < inCoreLimit)
349 return false;
350
351 switchToOverflow();
352 return true;
353 }
354
355 private void switchToOverflow() throws IOException {
356 overflow = overflow();
357
358 final Block last = blocks.remove(blocks.size() - 1);
359 for (final Block b : blocks)
360 overflow.write(b.buffer, 0, b.count);
361 blocks = null;
362
363 overflow = new SafeBufferedOutputStream(overflow, Block.SZ);
364 overflow.write(last.buffer, 0, last.count);
365 }
366
367 public void close() throws IOException {
368 if (overflow != null) {
369 try {
370 overflow.close();
371 } finally {
372 overflow = null;
373 }
374 }
375 }
376
377
378 public void destroy() {
379 blocks = null;
380
381 if (overflow != null) {
382 try {
383 overflow.close();
384 } catch (IOException err) {
385
386 } finally {
387 overflow = null;
388 }
389 }
390 }
391
392
393
394
395
396
397
398
399
400
401
402 public static class LocalFile extends TemporaryBuffer {
403
404 private final File directory;
405
406
407
408
409
410
411
412
413 private File onDiskFile;
414
415
416
417
418
419
420
421
422
423
424 public LocalFile(final File directory) {
425 this(directory, DEFAULT_IN_CORE_LIMIT);
426 }
427
428
429
430
431
432
433
434
435
436
437
438
439
440 public LocalFile(final File directory, final int inCoreLimit) {
441 super(inCoreLimit);
442 this.directory = directory;
443 }
444
445 protected OutputStream overflow() throws IOException {
446 onDiskFile = File.createTempFile("jgit_", ".buf", directory);
447 return new BufferedOutputStream(new FileOutputStream(onDiskFile));
448 }
449
450 public long length() {
451 if (onDiskFile == null) {
452 return super.length();
453 }
454 return onDiskFile.length();
455 }
456
457 public byte[] toByteArray() throws IOException {
458 if (onDiskFile == null) {
459 return super.toByteArray();
460 }
461
462 final long len = length();
463 if (Integer.MAX_VALUE < len)
464 throw new OutOfMemoryError(JGitText.get().lengthExceedsMaximumArraySize);
465 final byte[] out = new byte[(int) len];
466 final FileInputStream in = new FileInputStream(onDiskFile);
467 try {
468 IO.readFully(in, out, 0, (int) len);
469 } finally {
470 in.close();
471 }
472 return out;
473 }
474
475 public void writeTo(final OutputStream os, ProgressMonitor pm)
476 throws IOException {
477 if (onDiskFile == null) {
478 super.writeTo(os, pm);
479 return;
480 }
481 if (pm == null)
482 pm = NullProgressMonitor.INSTANCE;
483 final FileInputStream in = new FileInputStream(onDiskFile);
484 try {
485 int cnt;
486 final byte[] buf = new byte[Block.SZ];
487 while ((cnt = in.read(buf)) >= 0) {
488 os.write(buf, 0, cnt);
489 pm.update(cnt / 1024);
490 }
491 } finally {
492 in.close();
493 }
494 }
495
496 @Override
497 public InputStream openInputStream() throws IOException {
498 if (onDiskFile == null)
499 return super.openInputStream();
500 return new FileInputStream(onDiskFile);
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(final int limit) {
534 super(limit);
535 }
536
537
538
539
540
541
542
543
544
545
546
547
548
549 public Heap(final int estimatedSize, final 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 }