// // System.IO.StreamWriter.cs // // Authors: // Dietmar Maurer (dietmar@ximian.com) // Paolo Molaro (lupus@ximian.com) // // (C) Ximian, Inc. http://www.ximian.com // // // Copyright (C) 2004 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System.Text; using System; namespace System.IO { [Serializable] public class StreamWriter : TextWriter { private Encoding internalEncoding; private Stream internalStream; private bool closed = false; private bool iflush; private const int DefaultBufferSize = 1024; private const int DefaultFileBufferSize = 4096; private const int MinimumBufferSize = 256; private byte[] byte_buf; private int byte_pos; private char[] decode_buf; private int decode_pos; private bool DisposedAlready = false; private bool preamble_done = false; public new static readonly StreamWriter Null = new StreamWriter (Stream.Null, Encoding.UTF8Unmarked, 0); public StreamWriter (Stream stream) : this (stream, Encoding.UTF8Unmarked, DefaultBufferSize) {} public StreamWriter (Stream stream, Encoding encoding) : this (stream, encoding, DefaultBufferSize) {} internal void Initialize(Encoding encoding, int bufferSize) { internalEncoding = encoding; decode_pos = byte_pos = 0; int BufferSize = Math.Max(bufferSize, MinimumBufferSize); decode_buf = new char [BufferSize]; byte_buf = new byte [encoding.GetMaxByteCount (BufferSize)]; // Fixes bug http://bugzilla.ximian.com/show_bug.cgi?id=74513 if (internalStream.CanSeek && internalStream.Position > 0) preamble_done = true; } //[MonoTODO("Nothing is done with bufferSize")] public StreamWriter (Stream stream, Encoding encoding, int bufferSize) { if (null == stream) throw new ArgumentNullException("stream"); if (null == encoding) throw new ArgumentNullException("encoding"); if (bufferSize < 0) throw new ArgumentOutOfRangeException("bufferSize"); if (!stream.CanWrite) throw new ArgumentException("Can not write to stream", "stream"); internalStream = stream; Initialize(encoding, bufferSize); } public StreamWriter (string path) : this (path, false, Encoding.UTF8Unmarked, DefaultFileBufferSize) {} public StreamWriter (string path, bool append) : this (path, append, Encoding.UTF8Unmarked, DefaultFileBufferSize) {} public StreamWriter (string path, bool append, Encoding encoding) : this (path, append, encoding, DefaultFileBufferSize) {} public StreamWriter (string path, bool append, Encoding encoding, int bufferSize) { if (null == path) throw new ArgumentNullException("path"); if (String.Empty == path) throw new ArgumentException("path cannot be empty string"); if (path.IndexOfAny (Path.InvalidPathChars) != -1) throw new ArgumentException("path contains invalid characters"); if (null == encoding) throw new ArgumentNullException("encoding"); if (bufferSize < 0) throw new ArgumentOutOfRangeException("bufferSize"); string DirName = Path.GetDirectoryName(path); if (DirName != String.Empty && !Directory.Exists(DirName)) throw new DirectoryNotFoundException(); FileMode mode; if (append) mode = FileMode.Append; else mode = FileMode.Create; internalStream = new FileStream (path, mode, FileAccess.Write, FileShare.Read); if (append) internalStream.Position = internalStream.Length; else internalStream.SetLength (0); Initialize(encoding, bufferSize); } public virtual bool AutoFlush { get { return iflush; } set { if (DisposedAlready) throw new ObjectDisposedException("StreamWriter"); iflush = value; if (iflush) { Flush (); } } } public virtual Stream BaseStream { get { return internalStream; } } public override Encoding Encoding { get { return internalEncoding; } } protected override void Dispose (bool disposing) { if (!DisposedAlready && disposing && internalStream != null) { Flush(); DisposedAlready = true; internalStream.Close (); } internalStream = null; byte_buf = null; internalEncoding = null; decode_buf = null; } public override void Flush () { if (DisposedAlready) throw new ObjectDisposedException("StreamWriter"); Decode (); if (byte_pos > 0) { FlushBytes (); internalStream.Flush (); } } // how the speedup works: // the Write () methods simply copy the characters in a buffer of chars (decode_buf) // Decode () is called when the buffer is full or we need to flash. // Decode () will use the encoding to get the bytes and but them inside // byte_buf. From byte_buf the data is finally outputted to the stream. void FlushBytes () { // write the encoding preamble only at the start of the stream if (!preamble_done && byte_pos > 0) { byte[] preamble = internalEncoding.GetPreamble (); if (preamble.Length > 0) internalStream.Write (preamble, 0, preamble.Length); preamble_done = true; } internalStream.Write (byte_buf, 0, byte_pos); byte_pos = 0; } void Decode () { if (byte_pos > 0) FlushBytes (); if (decode_pos > 0) { int len = internalEncoding.GetBytes (decode_buf, 0, decode_pos, byte_buf, byte_pos); byte_pos += len; decode_pos = 0; } } public override void Write (char[] buffer, int index, int count) { if (DisposedAlready) throw new ObjectDisposedException("StreamWriter"); if (buffer == null) throw new ArgumentNullException ("buffer"); if (index < 0) throw new ArgumentOutOfRangeException ("index", "< 0"); if (count < 0) throw new ArgumentOutOfRangeException ("count", "< 0"); // re-ordered to avoid possible integer overflow if (index > buffer.Length - count) throw new ArgumentException ("index + count > buffer.Length"); LowLevelWrite (buffer, index, count); if (iflush) Flush(); } void LowLevelWrite (char[] buffer, int index, int count) { while (count > 0) { int todo = decode_buf.Length - decode_pos; if (todo == 0) { Decode (); todo = decode_buf.Length; } if (todo > count) todo = count; Buffer.BlockCopy (buffer, index * 2, decode_buf, decode_pos * 2, todo * 2); count -= todo; index += todo; decode_pos += todo; } } void LowLevelWrite (string s) { int count = s.Length; int index = 0; while (count > 0) { int todo = decode_buf.Length - decode_pos; if (todo == 0) { Decode (); todo = decode_buf.Length; } if (todo > count) todo = count; for (int i = 0; i < todo; i ++) decode_buf [i + decode_pos] = s [i + index]; count -= todo; index += todo; decode_pos += todo; } } public override void Write (char value) { if (DisposedAlready) throw new ObjectDisposedException("StreamWriter"); // the size of decode_buf is always > 0 and // we check for overflow right away if (decode_pos >= decode_buf.Length) Decode (); decode_buf [decode_pos++] = value; if (iflush) Flush (); } public override void Write (char[] value) { if (DisposedAlready) throw new ObjectDisposedException("StreamWriter"); if (value != null) LowLevelWrite (value, 0, value.Length); if (iflush) Flush (); } public override void Write (string value) { if (DisposedAlready) throw new ObjectDisposedException("StreamWriter"); if (value != null) LowLevelWrite (value); if (iflush) Flush (); } public override void Close() { closed = true; Dispose (true); } ~StreamWriter () { Dispose(false); } } }