2
This code is derived from jgit (http://eclipse.org/jgit).
3
Copyright owners are documented in jgit's IP log.
5
This program and the accompanying materials are made available
6
under the terms of the Eclipse Distribution License v1.0 which
7
accompanies this distribution, is reproduced below, and is
8
available at http://www.eclipse.org/org/documents/edl-v10.php
12
Redistribution and use in source and binary forms, with or
13
without modification, are permitted provided that the following
16
- Redistributions of source code must retain the above copyright
17
notice, this list of conditions and the following disclaimer.
19
- Redistributions in binary form must reproduce the above
20
copyright notice, this list of conditions and the following
21
disclaimer in the documentation and/or other materials provided
22
with the distribution.
24
- Neither the name of the Eclipse Foundation, Inc. nor the
25
names of its contributors may be used to endorse or promote
26
products derived from this software without specific prior
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
52
/// <summary>A fully buffered output stream.</summary>
54
/// A fully buffered output stream.
56
/// Subclasses determine the behavior when the in-memory buffer capacity has been
57
/// exceeded and additional bytes are still being received for output.
59
public abstract class TemporaryBuffer : OutputStream
61
/// <summary>Default limit for in-core storage.</summary>
62
/// <remarks>Default limit for in-core storage.</remarks>
63
protected internal const int DEFAULT_IN_CORE_LIMIT = 1024 * 1024;
65
/// <summary>Chain of data, if we are still completely in-core; otherwise null.</summary>
66
/// <remarks>Chain of data, if we are still completely in-core; otherwise null.</remarks>
67
private AList<TemporaryBuffer.Block> blocks;
69
/// <summary>Maximum number of bytes we will permit storing in memory.</summary>
71
/// Maximum number of bytes we will permit storing in memory.
73
/// When this limit is reached the data will be shifted to a file on disk,
74
/// preventing the JVM heap from growing out of control.
76
private int inCoreLimit;
80
/// <see cref="inCoreLimit">inCoreLimit</see>
81
/// has been reached, remainder goes here.
83
private OutputStream overflow;
85
/// <summary>Create a new empty temporary buffer.</summary>
86
/// <remarks>Create a new empty temporary buffer.</remarks>
87
/// <param name="limit">
88
/// maximum number of bytes to store in memory before entering the
89
/// overflow output path.
91
protected internal TemporaryBuffer(int limit)
97
/// <exception cref="System.IO.IOException"></exception>
98
public override void Write(int b)
100
if (overflow != null)
105
TemporaryBuffer.Block s = Last();
108
if (ReachedInCoreLimit())
113
s = new TemporaryBuffer.Block();
116
s.buffer[s.count++] = unchecked((byte)b);
119
/// <exception cref="System.IO.IOException"></exception>
120
public override void Write(byte[] b, int off, int len)
122
if (overflow == null)
126
TemporaryBuffer.Block s = Last();
129
if (ReachedInCoreLimit())
133
s = new TemporaryBuffer.Block();
136
int n = Math.Min(s.buffer.Length - s.count, len);
137
System.Array.Copy(b, off, s.buffer, s.count, n);
145
overflow.Write(b, off, len);
149
/// <summary>Dumps the entire buffer into the overflow stream, and flushes it.</summary>
150
/// <remarks>Dumps the entire buffer into the overflow stream, and flushes it.</remarks>
151
/// <exception cref="System.IO.IOException">
152
/// the overflow stream cannot be started, or the buffer contents
153
/// cannot be written to it, or it failed to flush.
155
protected internal virtual void DoFlush()
157
if (overflow == null)
164
/// <summary>Copy all bytes remaining on the input stream into this buffer.</summary>
165
/// <remarks>Copy all bytes remaining on the input stream into this buffer.</remarks>
166
/// <param name="in">the stream to read from, until EOF is reached.</param>
167
/// <exception cref="System.IO.IOException">
168
/// an error occurred reading from the input stream, or while
169
/// writing to a local temporary file.
171
public virtual void Copy(InputStream @in)
177
TemporaryBuffer.Block s = Last();
180
if (ReachedInCoreLimit())
184
s = new TemporaryBuffer.Block();
187
int n = @in.Read(s.buffer, s.count, s.buffer.Length - s.count);
195
byte[] tmp = new byte[TemporaryBuffer.Block.SZ];
197
while ((n_1 = @in.Read(tmp)) > 0)
199
overflow.Write(tmp, 0, n_1);
203
/// <summary>Obtain the length (in bytes) of the buffer.</summary>
205
/// Obtain the length (in bytes) of the buffer.
207
/// The length is only accurate after
208
/// <see cref="Close()">Close()</see>
209
/// has been invoked.
211
/// <returns>total length of the buffer, in bytes.</returns>
212
public virtual long Length()
214
return InCoreLength();
217
private long InCoreLength()
219
TemporaryBuffer.Block last = Last();
220
return ((long)blocks.Count - 1) * TemporaryBuffer.Block.SZ + last.count;
223
/// <summary>Convert this buffer's contents into a contiguous byte array.</summary>
225
/// Convert this buffer's contents into a contiguous byte array.
227
/// The buffer is only complete after
228
/// <see cref="Close()">Close()</see>
229
/// has been invoked.
232
/// the complete byte array; length matches
233
/// <see cref="Length()">Length()</see>
236
/// <exception cref="System.IO.IOException">an error occurred reading from a local temporary file
238
/// <exception cref="System.OutOfMemoryException">the buffer cannot fit in memory</exception>
239
public virtual byte[] ToByteArray()
242
if (int.MaxValue < len)
244
throw new OutOfMemoryException(JGitText.Get().lengthExceedsMaximumArraySize);
246
byte[] @out = new byte[(int)len];
248
foreach (TemporaryBuffer.Block b in blocks)
250
System.Array.Copy(b.buffer, 0, @out, outPtr, b.count);
256
/// <summary>Send this buffer to an output stream.</summary>
258
/// Send this buffer to an output stream.
260
/// This method may only be invoked after
261
/// <see cref="Close()">Close()</see>
263
/// normally, to ensure all data is completely transferred.
265
/// <param name="os">stream to send this buffer's complete content to.</param>
266
/// <param name="pm">
267
/// if not null progress updates are sent here. Caller should
268
/// initialize the task and the number of work units to <code>
269
/// <see cref="Length()">Length()</see>
272
/// <exception cref="System.IO.IOException">
273
/// an error occurred reading from a temporary file on the local
274
/// system, or writing to the output stream.
276
public virtual void WriteTo(OutputStream os, ProgressMonitor pm)
280
pm = NullProgressMonitor.INSTANCE;
282
foreach (TemporaryBuffer.Block b in blocks)
284
os.Write(b.buffer, 0, b.count);
285
pm.Update(b.count / 1024);
289
/// <summary>Open an input stream to read from the buffered data.</summary>
291
/// Open an input stream to read from the buffered data.
293
/// This method may only be invoked after
294
/// <see cref="Close()">Close()</see>
296
/// normally, to ensure all data is completely transferred.
299
/// a stream to read from the buffer. The caller must close the
300
/// stream when it is no longer useful.
302
/// <exception cref="System.IO.IOException">an error occurred opening the temporary file.
304
public virtual InputStream OpenInputStream()
306
return new TemporaryBuffer.BlockInputStream(this);
309
/// <summary>Reset this buffer for reuse, purging all buffered content.</summary>
310
/// <remarks>Reset this buffer for reuse, purging all buffered content.</remarks>
311
public virtual void Reset()
313
if (overflow != null)
317
if (inCoreLimit < TemporaryBuffer.Block.SZ)
319
blocks = new AList<TemporaryBuffer.Block>(1);
320
blocks.AddItem(new TemporaryBuffer.Block(inCoreLimit));
324
blocks = new AList<TemporaryBuffer.Block>(inCoreLimit / TemporaryBuffer.Block.SZ);
325
blocks.AddItem(new TemporaryBuffer.Block());
329
/// <summary>Open the overflow output stream, so the remaining output can be stored.</summary>
330
/// <remarks>Open the overflow output stream, so the remaining output can be stored.</remarks>
332
/// the output stream to receive the buffered content, followed by
333
/// the remaining output.
335
/// <exception cref="System.IO.IOException">the buffer cannot create the overflow stream.
337
protected internal abstract OutputStream Overflow();
339
private TemporaryBuffer.Block Last()
341
return blocks[blocks.Count - 1];
344
/// <exception cref="System.IO.IOException"></exception>
345
private bool ReachedInCoreLimit()
347
if (InCoreLength() < inCoreLimit)
355
/// <exception cref="System.IO.IOException"></exception>
356
private void SwitchToOverflow()
358
overflow = Overflow();
359
TemporaryBuffer.Block last = blocks.Remove(blocks.Count - 1);
360
foreach (TemporaryBuffer.Block b in blocks)
362
overflow.Write(b.buffer, 0, b.count);
365
overflow = new BufferedOutputStream(overflow, TemporaryBuffer.Block.SZ);
366
overflow.Write(last.buffer, 0, last.count);
369
/// <exception cref="System.IO.IOException"></exception>
370
public override void Close()
372
if (overflow != null)
385
/// <summary>Clear this buffer so it has no data, and cannot be used again.</summary>
386
/// <remarks>Clear this buffer so it has no data, and cannot be used again.</remarks>
387
public virtual void Destroy()
390
if (overflow != null)
401
// We shouldn't encounter an error closing the file.
407
/// <summary>A fully buffered output stream using local disk storage for large data.</summary>
409
/// A fully buffered output stream using local disk storage for large data.
411
/// Initially this output stream buffers to memory and is therefore similar
412
/// to ByteArrayOutputStream, but it shifts to using an on disk temporary
413
/// file if the output gets too large.
415
/// The content of this buffered stream may be sent to another OutputStream
416
/// only after this stream has been properly closed by
417
/// <see cref="TemporaryBuffer.Close()">TemporaryBuffer.Close()</see>
420
public class LocalFile : TemporaryBuffer
422
/// <summary>Directory to store the temporary file under.</summary>
423
/// <remarks>Directory to store the temporary file under.</remarks>
424
private readonly FilePath directory;
426
/// <summary>Location of our temporary file if we are on disk; otherwise null.</summary>
428
/// Location of our temporary file if we are on disk; otherwise null.
430
/// If we exceeded the
431
/// <see cref="TemporaryBuffer.inCoreLimit">TemporaryBuffer.inCoreLimit</see>
433
/// <see cref="TemporaryBuffer.blocks">TemporaryBuffer.blocks</see>
434
/// and created this file instead. All output goes here through
435
/// <see cref="TemporaryBuffer.overflow">TemporaryBuffer.overflow</see>
438
private FilePath onDiskFile;
440
/// <summary>Create a new temporary buffer.</summary>
441
/// <remarks>Create a new temporary buffer.</remarks>
442
public LocalFile() : this(null, DEFAULT_IN_CORE_LIMIT)
446
/// <summary>Create a new temporary buffer, limiting memory usage.</summary>
447
/// <remarks>Create a new temporary buffer, limiting memory usage.</remarks>
448
/// <param name="inCoreLimit">
449
/// maximum number of bytes to store in memory. Storage beyond
450
/// this limit will use the local file.
452
protected internal LocalFile(int inCoreLimit) : this(null, inCoreLimit)
456
/// <summary>Create a new temporary buffer, limiting memory usage.</summary>
457
/// <remarks>Create a new temporary buffer, limiting memory usage.</remarks>
458
/// <param name="directory">
459
/// if the buffer has to spill over into a temporary file, the
460
/// directory where the file should be saved. If null the
461
/// system default temporary directory (for example /tmp) will
464
public LocalFile(FilePath directory) : this(directory, DEFAULT_IN_CORE_LIMIT)
468
/// <summary>Create a new temporary buffer, limiting memory usage.</summary>
469
/// <remarks>Create a new temporary buffer, limiting memory usage.</remarks>
470
/// <param name="directory">
471
/// if the buffer has to spill over into a temporary file, the
472
/// directory where the file should be saved. If null the
473
/// system default temporary directory (for example /tmp) will
476
/// <param name="inCoreLimit">
477
/// maximum number of bytes to store in memory. Storage beyond
478
/// this limit will use the local file.
480
public LocalFile(FilePath directory, int inCoreLimit) : base(inCoreLimit)
482
this.directory = directory;
485
/// <exception cref="System.IO.IOException"></exception>
486
protected internal override OutputStream Overflow()
488
onDiskFile = FilePath.CreateTempFile("jgit_", ".buf", directory);
489
return new FileOutputStream(onDiskFile);
492
public override long Length()
494
if (onDiskFile == null)
496
return base.Length();
498
return onDiskFile.Length();
501
/// <exception cref="System.IO.IOException"></exception>
502
public override byte[] ToByteArray()
504
if (onDiskFile == null)
506
return base.ToByteArray();
509
if (int.MaxValue < len)
511
throw new OutOfMemoryException(JGitText.Get().lengthExceedsMaximumArraySize);
513
byte[] @out = new byte[(int)len];
514
FileInputStream @in = new FileInputStream(onDiskFile);
517
IOUtil.ReadFully(@in, @out, 0, (int)len);
526
/// <exception cref="System.IO.IOException"></exception>
527
public override void WriteTo(OutputStream os, ProgressMonitor pm)
529
if (onDiskFile == null)
531
base.WriteTo(os, pm);
536
pm = NullProgressMonitor.INSTANCE;
538
FileInputStream @in = new FileInputStream(onDiskFile);
542
byte[] buf = new byte[TemporaryBuffer.Block.SZ];
543
while ((cnt = @in.Read(buf)) >= 0)
545
os.Write(buf, 0, cnt);
546
pm.Update(cnt / 1024);
555
/// <exception cref="System.IO.IOException"></exception>
556
public override InputStream OpenInputStream()
558
if (onDiskFile == null)
560
return base.OpenInputStream();
562
return new FileInputStream(onDiskFile);
565
public override void Destroy()
568
if (onDiskFile != null)
572
if (!onDiskFile.Delete())
574
onDiskFile.DeleteOnExit();
585
/// <summary>A temporary buffer that will never exceed its in-memory limit.</summary>
587
/// A temporary buffer that will never exceed its in-memory limit.
589
/// If the in-memory limit is reached an IOException is thrown, rather than
590
/// attempting to spool to local disk.
592
public class Heap : TemporaryBuffer
594
/// <summary>Create a new heap buffer with a maximum storage limit.</summary>
595
/// <remarks>Create a new heap buffer with a maximum storage limit.</remarks>
596
/// <param name="limit">
597
/// maximum number of bytes that can be stored in this buffer.
598
/// Storing beyond this many will cause an IOException to be
599
/// thrown during write.
601
protected internal Heap(int limit) : base(limit)
605
/// <exception cref="System.IO.IOException"></exception>
606
protected internal override OutputStream Overflow()
608
throw new IOException(JGitText.Get().inMemoryBufferLimitExceeded);
614
internal const int SZ = 8 * 1024;
616
internal readonly byte[] buffer;
622
buffer = new byte[SZ];
625
internal Block(int sz)
627
buffer = new byte[sz];
630
internal virtual bool IsFull()
632
return count == buffer.Length;
636
private class BlockInputStream : InputStream
638
private byte[] singleByteBuffer;
640
private int blockIndex;
642
private TemporaryBuffer.Block block;
644
private int blockPos;
646
public BlockInputStream(TemporaryBuffer _enclosing)
648
this._enclosing = _enclosing;
649
this.block = this._enclosing.blocks[this.blockIndex];
652
/// <exception cref="System.IO.IOException"></exception>
653
public override int Read()
655
if (this.singleByteBuffer == null)
657
this.singleByteBuffer = new byte[1];
659
int n = this.Read(this.singleByteBuffer);
660
return n == 1 ? this.singleByteBuffer[0] & unchecked((int)(0xff)) : -1;
663
/// <exception cref="System.IO.IOException"></exception>
664
public override long Skip(long cnt)
669
int n = (int)Math.Min(this.block.count - this.blockPos, cnt);
678
if (this.NextBlock())
691
/// <exception cref="System.IO.IOException"></exception>
692
public override int Read(byte[] b, int off, int len)
701
int c = Math.Min(this.block.count - this.blockPos, len);
704
System.Array.Copy(this.block.buffer, this.blockPos, b, off, c);
711
if (this.NextBlock())
721
return 0 < copied ? copied : -1;
724
private bool NextBlock()
726
if (++this.blockIndex < this._enclosing.blocks.Count)
728
this.block = this._enclosing.blocks[this.blockIndex];
735
private readonly TemporaryBuffer _enclosing;