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