// ==++== // // Copyright (c) Microsoft Corporation. All rights reserved. // // ==--== /*============================================================ ** ** Class: StreamWriter ** ** ** Purpose: For writing text to streams in a particular ** encoding. ** ** Date: February 21, 2000 ** ===========================================================*/ using System; using System.Diagnostics; using System.Text; namespace System.IO { // This class implements a TextWriter for writing characters to a Stream. // This is designed for character output in a particular Encoding, // whereas the Stream class is designed for byte input and output. // //| public class StreamWriter : TextWriter { // For UTF-8, the values of 1K for the default buffer size and 4K for the // file stream buffer size are reasonable & give very reasonable // performance for in terms of construction time for the StreamWriter and // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer, // which means we take advantage of Anders' adaptive buffering code. // The performance using UnicodeEncoding is acceptable. -- Brian 7/9/2001 private const int DefaultBufferSize = 1024; // char[] private const int DefaultFileStreamBufferSize = 4096; private const int MinBufferSize = 128; #if DONT // Bit bucket - Null has no backing store. //| public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, new UTF8Encoding(false, true), 128); #endif internal Stream stream; private Encoding encoding; private Encoder encoder; internal byte[] byteBuffer; internal char[] charBuffer; internal int charPos; internal int charLen; internal bool autoFlush; private bool haveWrittenPreamble; private bool closable; // For Console.Out - should Finalize call Dispose? #if _DEBUG private String allocatedFrom = String.Empty; #endif [Microsoft.Contracts.NotDelayed] internal StreamWriter() { } //| [Microsoft.Contracts.NotDelayed] public StreamWriter(Stream stream) : this(stream, new UTF8Encoding(false, true), DefaultBufferSize) { } //| [Microsoft.Contracts.NotDelayed] public StreamWriter(Stream stream, Encoding encoding) : this(stream, encoding, DefaultBufferSize) { } // Creates a new StreamWriter for the given stream. The // character encoding is set by encoding and the buffer size, // in number of 16-bit characters, is set by bufferSize. // //| [Microsoft.Contracts.NotDelayed] public StreamWriter(Stream stream, Encoding encoding, int bufferSize) { if (stream==null || encoding==null) throw new ArgumentNullException((stream==null ? "stream" : "encoding")); if (!stream.CanWrite) throw new ArgumentException("Argument_StreamNotWritable"); if (bufferSize <= 0) throw new ArgumentOutOfRangeException("ArgumentOutOfRange_NeedPosNum"); Init(stream, encoding, bufferSize); } //| [Microsoft.Contracts.NotDelayed] public StreamWriter(String path) : this(path, false, new UTF8Encoding(false, true), DefaultBufferSize) { } //| [Microsoft.Contracts.NotDelayed] public StreamWriter(String path, bool append) : this(path, append, new UTF8Encoding(false, true), DefaultBufferSize) { } //| [Microsoft.Contracts.NotDelayed] public StreamWriter(String path, bool append, Encoding encoding) : this(path, append, encoding, DefaultBufferSize) { } //| [Microsoft.Contracts.NotDelayed] public StreamWriter(String path, bool append, Encoding encoding, int bufferSize) { if (path==null || encoding==null) throw new ArgumentNullException((path==null ? "path" : "encoding")); if (bufferSize <= 0) throw new ArgumentOutOfRangeException("ArgumentOutOfRange_NeedPosNum"); Stream stream = CreateFile(path, append); Init(stream, encoding, bufferSize); } private void Init(Stream! stream, Encoding! encoding, int bufferSize) { this.stream = stream; this.encoding = encoding; this.encoder = encoding.GetEncoder(); if (bufferSize < MinBufferSize) bufferSize = MinBufferSize; charBuffer = new char[bufferSize]; byteBuffer = new byte[encoding.GetMaxByteCount(bufferSize)]; charLen = bufferSize; // If we're appending to a Stream that already has data, don't write // the preamble junk. if (stream.CanSeek && stream.Position > 0) haveWrittenPreamble = true; closable = true; } private static Stream! CreateFile(String path, bool append) { FileMode mode = append? FileMode.Append: FileMode.Create; FileStream f = new FileStream(path, mode, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize); return f; } //| public override void Close() { Dispose(true); } //| protected override void Dispose(bool disposing) { // Dispose of our resources if this StreamWriter is closable. // Note that Console.Out should not be closable. if (disposing) { if (stream != null) { Flush(true, true); if (Closable) stream.Close(); } } if (Closable && stream != null) { stream = null; byteBuffer = null; charBuffer = null; encoding = null; encoder = null; charLen = 0; base.Dispose(disposing); } } //| ~StreamWriter() { // Make sure people closed this StreamWriter, but of course, make // allowances for BufferedStream (used by Console.Out & Error) and // StreamWriter::Null. #if _DEBUG Debug.Correctness(charPos == 0, "You didn't close a StreamWriter, and you lost data!\r\nStream type: "+(stream==null ? "" : stream.GetType().FullName)+((stream != null && stream is FileStream) ? " File name: "+((FileStream)stream).NameInternal : "")+"\r\nAllocated from:\r\n"+allocatedFrom); #endif Dispose(false); } //| public override void Flush() { Flush(true, true); } private void Flush(bool flushStream, bool flushEncoder) { // flushEncoder should be true at the end of the file and if // the user explicitly calls Flush (though not if AutoFlush is true). // This is required to flush any dangling characters from our UTF-7 // and UTF-8 encoders. if (stream == null) __Error.WriterClosed(); // Perf boost for Flush on non-dirty writers. if (charPos==0 && !flushStream && !flushEncoder) return; if (!haveWrittenPreamble) { haveWrittenPreamble = true; byte[] preamble = encoding.GetPreamble(); if (preamble.Length > 0) stream.Write(preamble, 0, preamble.Length); } int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder); charPos = 0; if (count > 0) stream.Write(byteBuffer, 0, count); // By definition, calling Flush should flush the stream, but this is // only necessary if we passed in true for flushStream. The Web // Services guys have some perf tests where flushing needlessly hurts. if (flushStream) stream.Flush(); } //| public virtual bool AutoFlush { get { return autoFlush; } set { autoFlush = value; if (value) Flush(true, false); } } //| public virtual Stream BaseStream { get { return stream; } } internal bool Closable { get { return closable; } set { closable = value; } } //| public override Encoding Encoding { get { return encoding; } } //| public override void Write(char value) { if (charPos == charLen) Flush(false, false); charBuffer[charPos++] = value; if (autoFlush) Flush(true, false); } //| public override void Write(char[] buffer) { if (buffer == null) { return; } int index = 0; int count = buffer.Length; while (count > 0) { int limit = this.charLen - this.charPos; if (limit <= 0) { this.Flush(false, false); limit = this.charLen - this.charPos; } if (limit > count) { limit = count; } Buffer.BlockCopy(buffer, index*2, charBuffer, charPos*2, limit*2); this.charPos += limit; index += limit; count -= limit; } if (this.autoFlush) { this.Flush(true, false); } } //| public override void Write(char[] buffer, int index, int count) { if (buffer == null) { throw new ArgumentNullException("buffer", "Buffer is null"); } if (index < 0) { throw new ArgumentOutOfRangeException("index", "index is negative"); } if (count < 0) { throw new ArgumentOutOfRangeException("count", "count is negative"); } if (buffer.Length < index + count) { throw new ArgumentException("index + count greater than buffer length"); } while (count > 0) { int limit = this.charLen - this.charPos; if (limit <= 0) { this.Flush(false, false); limit = this.charLen - this.charPos; } if (limit > count) { limit = count; } for (int i = 0; i < limit; i++) { charBuffer[this.charPos + i] = buffer[index + i]; } this.charPos += limit; index += limit; count -= limit; } if (this.autoFlush) { this.Flush(true, false); } } //| public override void Write(String value) { if (value != null) { int count = value.Length; int index = 0; while (count > 0) { if (charPos == charLen) Flush(false, false); int n = charLen - charPos; if (n > count) n = count; Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code."); value.CopyTo(index, charBuffer, charPos, n); charPos += n; index += n; count -= n; } if (autoFlush) Flush(true, false); } } /* // This method is more efficient for long strings outputted to streams // than the one on TextWriter, and won't cause any problems in terms of // hiding methods on TextWriter as long as languages respect the // hide-by-name-and-sig metadata flag. public override void WriteLine(String value) { if (value != null) { int count = value.Length; int index = 0; while (count > 0) { if (charPos == charLen) Flush(false); int n = charLen - charPos; if (n > count) n = count; Debug.Assert(n > 0, "StreamWriter::WriteLine(String) isn't making progress! This is most likely a race condition in user code."); value.CopyTo(index, charBuffer, charPos, n); charPos += n; index += n; count -= n; } } if (charPos >= charLen - 2) Flush(false); Buffer.BlockCopy(CoreNewLine, 0, charBuffer, charPos*2, CoreNewLine.Length * 2); charPos += CoreNewLine.Length; if (autoFlush) Flush(true, false); } */ } }