X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2Fcorlib%2FSystem.IO%2FStreamReader.cs;h=0c67b225ba9b478b62a0a92688b74a77607d9ba2;hb=77d2f31ac15486f20162c0d4455a62d2191e6b16;hp=76418bc24dab6eaf90b4b33dbba98bfbbc1ab054;hpb=3d693eeb90339833968d66c3dc9fde2fa3ba2cef;p=mono.git diff --git a/mcs/class/corlib/System.IO/StreamReader.cs b/mcs/class/corlib/System.IO/StreamReader.cs index 76418bc24da..0c67b225ba9 100644 --- a/mcs/class/corlib/System.IO/StreamReader.cs +++ b/mcs/class/corlib/System.IO/StreamReader.cs @@ -1,16 +1,14 @@ // // System.IO.StreamReader.cs // -// Author: +// Authors: // Dietmar Maurer (dietmar@ximian.com) // Miguel de Icaza (miguel@ximian.com) +// Marek Safar (marek.safar@gmail.com) // // (C) Ximian, Inc. http://www.ximian.com // Copyright (C) 2004 Novell (http://www.novell.com) -// - -// -// Copyright (C) 2004 Novell, Inc (http://www.novell.com) +// Copyright 2011, 2013 Xamarin Inc. // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -35,10 +33,50 @@ using System; using System.Text; using System.Runtime.InteropServices; +#if NET_4_5 +using System.Threading.Tasks; +#endif namespace System.IO { [Serializable] - public class StreamReader : TextReader { + [ComVisible (true)] + public class StreamReader : TextReader + { + sealed class NullStreamReader : StreamReader + { + public override int Peek () + { + return -1; + } + + public override int Read () + { + return -1; + } + + public override int Read ([In, Out] char[] buffer, int index, int count) + { + return 0; + } + + public override string ReadLine () + { + return null; + } + + public override string ReadToEnd () + { + return String.Empty; + } + + public override Stream BaseStream { + get { return Stream.Null; } + } + + public override Encoding CurrentEncoding { + get { return Encoding.Unicode; } + } + } const int DefaultBufferSize = 1024; const int DefaultFileBufferSize = 4096; @@ -48,11 +86,21 @@ namespace System.IO { // The input buffer // byte [] input_buffer; + + // Input buffer ready for recycling + static byte [] input_buffer_recycle; + static object input_buffer_recycle_lock = new object (); // // The decoded buffer from the above input buffer // char [] decoded_buffer; + static char[] decoded_buffer_recycle; + + Encoding encoding; + Decoder decoder; + StringBuilder line_builder; + Stream base_stream; // // Decoded bytes in decoded_buffer. @@ -71,84 +119,61 @@ namespace System.IO { int do_checks; - Encoding encoding; - Decoder decoder; - - Stream base_stream; bool mayBlock; - StringBuilder line_builder; - private class NullStreamReader : StreamReader { - public override int Peek () - { - return -1; - } - - public override int Read () - { - return -1; - } - - public override int Read ([In, Out] char[] buffer, int index, int count) - { - return 0; - } - - public override string ReadLine () - { - return null; - } - - public override string ReadToEnd () - { - return String.Empty; - } - - public override Stream BaseStream - { - get { return Stream.Null; } - } - - public override Encoding CurrentEncoding - { - get { return Encoding.Unicode; } - } - } +#if NET_4_5 + IDecoupledTask async_task; + readonly bool leave_open; +#endif - public new static readonly StreamReader Null = (StreamReader)(new NullStreamReader()); + public new static readonly StreamReader Null = new NullStreamReader (); - internal StreamReader() {} + private StreamReader() {} public StreamReader(Stream stream) : this (stream, Encoding.UTF8Unmarked, true, DefaultBufferSize) { } - public StreamReader(Stream stream, bool detect_encoding_from_bytemarks) - : this (stream, Encoding.UTF8Unmarked, detect_encoding_from_bytemarks, DefaultBufferSize) { } + public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks) + : this (stream, Encoding.UTF8Unmarked, detectEncodingFromByteOrderMarks, DefaultBufferSize) { } public StreamReader(Stream stream, Encoding encoding) : this (stream, encoding, true, DefaultBufferSize) { } - public StreamReader(Stream stream, Encoding encoding, bool detect_encoding_from_bytemarks) - : this (stream, encoding, detect_encoding_from_bytemarks, DefaultBufferSize) { } - - public StreamReader(Stream stream, Encoding encoding, bool detect_encoding_from_bytemarks, int buffer_size) + public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks) + : this (stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize) { } + +#if NET_4_5 + public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) + : this (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false) { - Initialize (stream, encoding, detect_encoding_from_bytemarks, buffer_size); + } + + public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen) +#else + const bool leave_open = false; + + public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) +#endif + { +#if NET_4_5 + leave_open = leaveOpen; +#endif + Initialize (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize); } public StreamReader(string path) : this (path, Encoding.UTF8Unmarked, true, DefaultFileBufferSize) { } - public StreamReader(string path, bool detect_encoding_from_bytemarks) - : this (path, Encoding.UTF8Unmarked, detect_encoding_from_bytemarks, DefaultFileBufferSize) { } + public StreamReader(string path, bool detectEncodingFromByteOrderMarks) + : this (path, Encoding.UTF8Unmarked, detectEncodingFromByteOrderMarks, DefaultFileBufferSize) { } public StreamReader(string path, Encoding encoding) : this (path, encoding, true, DefaultFileBufferSize) { } - public StreamReader(string path, Encoding encoding, bool detect_encoding_from_bytemarks) - : this (path, encoding, detect_encoding_from_bytemarks, DefaultFileBufferSize) { } + public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks) + : this (path, encoding, detectEncodingFromByteOrderMarks, DefaultFileBufferSize) { } - public StreamReader(string path, Encoding encoding, bool detect_encoding_from_bytemarks, int buffer_size) + public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) { if (null == path) throw new ArgumentNullException("path"); @@ -158,14 +183,14 @@ namespace System.IO { throw new ArgumentException("path contains invalid characters"); if (null == encoding) throw new ArgumentNullException ("encoding"); - if (buffer_size <= 0) - throw new ArgumentOutOfRangeException ("buffer_size", "The minimum size of the buffer must be positive"); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException ("bufferSize", "The minimum size of the buffer must be positive"); Stream stream = (Stream) File.OpenRead (path); - Initialize (stream, encoding, detect_encoding_from_bytemarks, buffer_size); + Initialize (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize); } - internal void Initialize (Stream stream, Encoding encoding, bool detect_encoding_from_bytemarks, int buffer_size) + internal void Initialize (Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) { if (null == stream) throw new ArgumentNullException ("stream"); @@ -173,36 +198,65 @@ namespace System.IO { throw new ArgumentNullException ("encoding"); if (!stream.CanRead) throw new ArgumentException ("Cannot read stream"); - if (buffer_size <= 0) - throw new ArgumentOutOfRangeException ("buffer_size", "The minimum size of the buffer must be positive"); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException ("bufferSize", "The minimum size of the buffer must be positive"); - if (buffer_size < MinimumBufferSize) - buffer_size = MinimumBufferSize; + if (bufferSize < MinimumBufferSize) + bufferSize = MinimumBufferSize; + + // since GetChars() might add flushed character, it + // should have additional char buffer for extra 1 + // (probably 1 is ok, but might be insufficient. I'm not sure) + var decoded_buffer_size = encoding.GetMaxCharCount (bufferSize) + 1; + + // + // Instead of allocating a new default buffer use the + // last one if there is any available + // + if (bufferSize <= DefaultBufferSize && input_buffer_recycle != null) { + lock (input_buffer_recycle_lock) { + if (input_buffer_recycle != null) { + input_buffer = input_buffer_recycle; + input_buffer_recycle = null; + } + + if (decoded_buffer_recycle != null && decoded_buffer_size <= decoded_buffer_recycle.Length) { + decoded_buffer = decoded_buffer_recycle; + decoded_buffer_recycle = null; + } + } + } + + if (input_buffer == null) + input_buffer = new byte [bufferSize]; + else + Array.Clear (input_buffer, 0, bufferSize); + + if (decoded_buffer == null) + decoded_buffer = new char [decoded_buffer_size]; + else + Array.Clear (decoded_buffer, 0, decoded_buffer_size); - base_stream = stream; - input_buffer = new byte [buffer_size]; - this.buffer_size = buffer_size; + base_stream = stream; + this.buffer_size = bufferSize; this.encoding = encoding; decoder = encoding.GetDecoder (); byte [] preamble = encoding.GetPreamble (); - do_checks = detect_encoding_from_bytemarks ? 1 : 0; + do_checks = detectEncodingFromByteOrderMarks ? 1 : 0; do_checks += (preamble.Length == 0) ? 0 : 2; - decoded_buffer = new char [encoding.GetMaxCharCount (buffer_size)]; decoded_count = 0; pos = 0; } - public virtual Stream BaseStream - { + public virtual Stream BaseStream { get { return base_stream; } } - public virtual Encoding CurrentEncoding - { + public virtual Encoding CurrentEncoding { get { if (encoding == null) throw new Exception (); @@ -210,11 +264,9 @@ namespace System.IO { } } -#if NET_2_0 public bool EndOfStream { get { return Peek () < 0; } } -#endif public override void Close () { @@ -223,9 +275,21 @@ namespace System.IO { protected override void Dispose (bool disposing) { - if (disposing && base_stream != null) + if (disposing && base_stream != null && !leave_open) base_stream.Close (); + if (input_buffer != null && input_buffer.Length == DefaultBufferSize && input_buffer_recycle == null) { + lock (input_buffer_recycle_lock) { + if (input_buffer_recycle == null) { + input_buffer_recycle = input_buffer; + } + + if (decoded_buffer_recycle == null) { + decoded_buffer_recycle = decoded_buffer; + } + } + } + input_buffer = null; decoded_buffer = null; encoding = null; @@ -259,17 +323,15 @@ namespace System.IO { if (count < 2) return 0; -#if !NET_2_0 - if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe){ - this.encoding = Encoding.Unicode; - return 2; - } -#endif - if (input_buffer [0] == 0xfe && input_buffer [1] == 0xff){ this.encoding = Encoding.BigEndianUnicode; return 2; } + if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe && count < 4) { + // If we don't have enough bytes we can't check for UTF32, so use Unicode + this.encoding = Encoding.Unicode; + return 2; + } if (count < 3) return 0; @@ -279,7 +341,6 @@ namespace System.IO { return 3; } -#if NET_2_0 if (count < 4) { if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe && input_buffer [2] != 0) { this.encoding = Encoding.Unicode; @@ -304,7 +365,6 @@ namespace System.IO { this.encoding = Encoding.Unicode; return 2; } -#endif } return 0; @@ -312,6 +372,8 @@ namespace System.IO { public void DiscardBufferedData () { + CheckState (); + pos = decoded_count = 0; mayBlock = false; // Discard internal state of the decoder too. @@ -319,81 +381,103 @@ namespace System.IO { } // the buffer is empty, fill it again - private int ReadBuffer () + // Keep in sync with ReadBufferAsync + int ReadBuffer () { pos = 0; - int cbEncoded = 0; // keep looping until the decoder gives us some chars decoded_count = 0; - int parse_start = 0; - do - { - cbEncoded = base_stream.Read (input_buffer, 0, buffer_size); - + do { + var cbEncoded = base_stream.Read (input_buffer, 0, buffer_size); if (cbEncoded <= 0) return 0; - mayBlock = (cbEncoded < buffer_size); - if (do_checks > 0){ - Encoding old = encoding; - parse_start = DoChecks (cbEncoded); - if (old != encoding){ - decoder = encoding.GetDecoder (); - } - do_checks = 0; - cbEncoded -= parse_start; - } - - decoded_count += decoder.GetChars (input_buffer, parse_start, cbEncoded, decoded_buffer, 0); - parse_start = 0; + decoded_count = ReadBufferCore (cbEncoded); } while (decoded_count == 0); return decoded_count; } + int ReadBufferCore (int cbEncoded) + { + int parse_start; + + mayBlock = cbEncoded < buffer_size; + if (do_checks > 0){ + Encoding old = encoding; + parse_start = DoChecks (cbEncoded); + if (old != encoding){ + int old_decoded_size = old.GetMaxCharCount (buffer_size) + 1; + int new_decoded_size = encoding.GetMaxCharCount (buffer_size) + 1; + if (old_decoded_size != new_decoded_size) + decoded_buffer = new char [new_decoded_size]; + decoder = encoding.GetDecoder (); + } + do_checks = 0; + cbEncoded -= parse_start; + } else { + parse_start = 0; + } + + return decoder.GetChars (input_buffer, parse_start, cbEncoded, decoded_buffer, 0); + } + + // + // Peek can block: + // http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=96484 + // public override int Peek () { - if (base_stream == null) - throw new ObjectDisposedException ("StreamReader", "Cannot read from a closed StreamReader"); - if (pos >= decoded_count && (mayBlock || ReadBuffer () == 0)) + CheckState (); + + if (pos >= decoded_count && ReadBuffer () == 0) return -1; return decoded_buffer [pos]; } + // + // Used internally by our console, as it previously depended on Peek() being a + // routine that would not block. + // + internal bool DataAvailable () + { + return pos < decoded_count; + } + public override int Read () { - if (base_stream == null) - throw new ObjectDisposedException ("StreamReader", "Cannot read from a closed StreamReader"); + CheckState (); + if (pos >= decoded_count && ReadBuffer () == 0) return -1; return decoded_buffer [pos++]; } - public override int Read ([In, Out] char[] dest_buffer, int index, int count) + // Keep in sync with ReadAsync + public override int Read ([In, Out] char[] buffer, int index, int count) { - if (base_stream == null) - throw new ObjectDisposedException ("StreamReader", "Cannot read from a closed StreamReader"); - if (dest_buffer == null) - throw new ArgumentNullException ("dest_buffer"); + 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 > dest_buffer.Length - count) - throw new ArgumentException ("index + count > dest_buffer.Length"); + if (index > buffer.Length - count) + throw new ArgumentException ("index + count > buffer.Length"); + + CheckState (); int chars_read = 0; - while (count > 0) - { + while (count > 0) { if (pos >= decoded_count && ReadBuffer () == 0) return chars_read > 0 ? chars_read : 0; int cch = Math.Min (decoded_count - pos, count); - Array.Copy (decoded_buffer, pos, dest_buffer, index, cch); + Array.Copy (decoded_buffer, pos, buffer, index, cch); pos += cch; index += cch; count -= cch; @@ -420,6 +504,10 @@ namespace System.IO { return res; } else if (foundCR) { foundCR = false; + if (pos == 0) + return -2; // Need to flush the current buffered line. + // This is a \r at the end of the previous decoded buffer that + // is not followed by a \n in the current decoded buffer. return pos - 1; } @@ -429,10 +517,10 @@ namespace System.IO { return -1; } + // Keep in sync with ReadLineAsync public override string ReadLine() { - if (base_stream == null) - throw new ObjectDisposedException ("StreamReader", "Cannot read from a closed StreamReader"); + CheckState (); if (pos >= decoded_count && ReadBuffer () == 0) return null; @@ -441,6 +529,8 @@ namespace System.IO { int end = FindNextEOL (); if (end < decoded_count && end >= begin) return new string (decoded_buffer, begin, end - begin); + if (end == -2) + return line_builder.ToString (0, line_builder.Length); if (line_builder == null) line_builder = new StringBuilder (); @@ -472,24 +562,207 @@ namespace System.IO { } return line_builder.ToString (0, line_builder.Length); } + + if (end == -2) + return line_builder.ToString (0, line_builder.Length); } } + // Keep in sync with ReadToEndAsync public override string ReadToEnd() + { + CheckState (); + + StringBuilder text = new StringBuilder (); + + do { + text.Append (decoded_buffer, pos, decoded_count - pos); + } while (ReadBuffer () != 0); + + return text.ToString (); + } + + void CheckState () { if (base_stream == null) throw new ObjectDisposedException ("StreamReader", "Cannot read from a closed StreamReader"); +#if NET_4_5 + if (async_task != null && !async_task.IsCompleted) + throw new InvalidOperationException (); +#endif + } + +#if NET_4_5 + public override int ReadBlock ([In, Out] char[] buffer, int index, int count) + { + 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"); + + CheckState (); + + return base.ReadBlock (buffer, index, count); + } + + public override Task ReadAsync (char[] buffer, int index, int count) + { + 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"); + + CheckState (); + + DecoupledTask res; + async_task = res = new DecoupledTask (ReadAsyncCore (buffer, index, count)); + return res.Task; + } + + async Task ReadAsyncCore (char[] buffer, int index, int count) + { + int chars_read = 0; + + while (count > 0) { + if (pos >= decoded_count && await ReadBufferAsync ().ConfigureAwait (false) == 0) + return chars_read > 0 ? chars_read : 0; + + int cch = Math.Min (decoded_count - pos, count); + Array.Copy (decoded_buffer, pos, buffer, index, cch); + pos += cch; + index += cch; + count -= cch; + chars_read += cch; + if (mayBlock) + break; + } + + return chars_read; + } + + public override Task ReadBlockAsync (char[] buffer, int index, int count) + { + 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"); + + CheckState (); + + DecoupledTask res; + async_task = res = new DecoupledTask (ReadAsyncCore (buffer, index, count)); + return res.Task; + } + + public override Task ReadLineAsync () + { + CheckState (); + + DecoupledTask res; + async_task = res = new DecoupledTask (ReadLineAsyncCore ()); + return res.Task; + } + + async Task ReadLineAsyncCore () + { + if (pos >= decoded_count && await ReadBufferAsync ().ConfigureAwait (false) == 0) + return null; + + int begin = pos; + int end = FindNextEOL (); + if (end < decoded_count && end >= begin) + return new string (decoded_buffer, begin, end - begin); + if (end == -2) + return line_builder.ToString (0, line_builder.Length); + + if (line_builder == null) + line_builder = new StringBuilder (); + else + line_builder.Length = 0; + + while (true) { + if (foundCR) // don't include the trailing CR if present + decoded_count--; + + line_builder.Append (decoded_buffer, begin, decoded_count - begin); + if (await ReadBufferAsync ().ConfigureAwait (false) == 0) { + if (line_builder.Capacity > 32768) { + StringBuilder sb = line_builder; + line_builder = null; + return sb.ToString (0, sb.Length); + } + return line_builder.ToString (0, line_builder.Length); + } + + begin = pos; + end = FindNextEOL (); + if (end < decoded_count && end >= begin) { + line_builder.Append (decoded_buffer, begin, end - begin); + if (line_builder.Capacity > 32768) { + StringBuilder sb = line_builder; + line_builder = null; + return sb.ToString (0, sb.Length); + } + return line_builder.ToString (0, line_builder.Length); + } + + if (end == -2) + return line_builder.ToString (0, line_builder.Length); + } + } + + public override Task ReadToEndAsync () + { + CheckState (); + + DecoupledTask res; + async_task = res = new DecoupledTask (ReadToEndAsyncCore ()); + return res.Task; + } + + async Task ReadToEndAsyncCore () + { StringBuilder text = new StringBuilder (); - int size = decoded_buffer.Length; - char [] buffer = new char [size]; - int len; - - while ((len = Read (buffer, 0, size)) > 0) - text.Append (buffer, 0, len); + do { + text.Append (decoded_buffer, pos, decoded_count - pos); + } while (await ReadBufferAsync () != 0); return text.ToString (); } + + async Task ReadBufferAsync () + { + pos = 0; + + // keep looping until the decoder gives us some chars + decoded_count = 0; + do { + var cbEncoded = await base_stream.ReadAsync (input_buffer, 0, buffer_size).ConfigureAwait (false); + if (cbEncoded <= 0) + return 0; + + decoded_count = ReadBufferCore (cbEncoded); + } while (decoded_count == 0); + + return decoded_count; + } +#endif } }