2 // System.IO.StreamReader.cs
5 // Dietmar Maurer (dietmar@ximian.com)
6 // Miguel de Icaza (miguel@ximian.com)
7 // Marek Safar (marek.safar@gmail.com)
9 // (C) Ximian, Inc. http://www.ximian.com
10 // Copyright (C) 2004 Novell (http://www.novell.com)
11 // Copyright 2011, 2013 Xamarin Inc.
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Runtime.InteropServices;
37 using System.Threading.Tasks;
43 public class StreamReader : TextReader
45 sealed class NullStreamReader : StreamReader
47 internal NullStreamReader ()
49 base_stream = Stream.Null;
52 public override int Peek ()
57 public override int Read ()
62 public override int Read ([In, Out] char[] buffer, int index, int count)
67 public override string ReadLine ()
72 public override string ReadToEnd ()
77 public override Stream BaseStream {
78 get { return Stream.Null; }
81 public override Encoding CurrentEncoding {
82 get { return Encoding.Unicode; }
86 const int DefaultBufferSize = 1024;
87 const int DefaultFileBufferSize = 4096;
88 const int MinimumBufferSize = 128;
95 // Input buffer ready for recycling
96 static byte [] input_buffer_recycle;
97 static object input_buffer_recycle_lock = new object ();
100 // The decoded buffer from the above input buffer
102 char [] decoded_buffer;
103 static char[] decoded_buffer_recycle;
107 StringBuilder line_builder;
111 // Decoded bytes in decoded_buffer.
116 // Current position in the decoded_buffer
121 // The buffer size that we are using
130 IDecoupledTask async_task;
131 readonly bool leave_open;
134 public new static readonly StreamReader Null = new NullStreamReader ();
136 private StreamReader() {}
138 public StreamReader(Stream stream)
139 : this (stream, Encoding.UTF8, true, DefaultBufferSize) { }
141 public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
142 : this (stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize) { }
144 public StreamReader(Stream stream, Encoding encoding)
145 : this (stream, encoding, true, DefaultBufferSize) { }
147 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks)
148 : this (stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize) { }
151 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
152 : this (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false)
156 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen)
158 const bool leave_open = false;
160 public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
164 leave_open = leaveOpen;
166 Initialize (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize);
169 public StreamReader(string path)
170 : this (path, Encoding.UTF8, true, DefaultFileBufferSize) { }
172 public StreamReader(string path, bool detectEncodingFromByteOrderMarks)
173 : this (path, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultFileBufferSize) { }
175 public StreamReader(string path, Encoding encoding)
176 : this (path, encoding, true, DefaultFileBufferSize) { }
178 public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks)
179 : this (path, encoding, detectEncodingFromByteOrderMarks, DefaultFileBufferSize) { }
181 public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
184 throw new ArgumentNullException("path");
185 if (String.Empty == path)
186 throw new ArgumentException("Empty path not allowed");
187 if (path.IndexOfAny (Path.InvalidPathChars) != -1)
188 throw new ArgumentException("path contains invalid characters");
189 if (null == encoding)
190 throw new ArgumentNullException ("encoding");
192 throw new ArgumentOutOfRangeException ("bufferSize", "The minimum size of the buffer must be positive");
194 Stream stream = (Stream) File.OpenRead (path);
195 Initialize (stream, encoding, detectEncodingFromByteOrderMarks, bufferSize);
198 internal void Initialize (Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
201 throw new ArgumentNullException ("stream");
202 if (null == encoding)
203 throw new ArgumentNullException ("encoding");
205 throw new ArgumentException ("Cannot read stream");
207 throw new ArgumentOutOfRangeException ("bufferSize", "The minimum size of the buffer must be positive");
209 if (bufferSize < MinimumBufferSize)
210 bufferSize = MinimumBufferSize;
212 // since GetChars() might add flushed character, it
213 // should have additional char buffer for extra 1
214 // (probably 1 is ok, but might be insufficient. I'm not sure)
215 var decoded_buffer_size = encoding.GetMaxCharCount (bufferSize) + 1;
218 // Instead of allocating a new default buffer use the
219 // last one if there is any available
221 if (bufferSize <= DefaultBufferSize && input_buffer_recycle != null) {
222 lock (input_buffer_recycle_lock) {
223 if (input_buffer_recycle != null) {
224 input_buffer = input_buffer_recycle;
225 input_buffer_recycle = null;
228 if (decoded_buffer_recycle != null && decoded_buffer_size <= decoded_buffer_recycle.Length) {
229 decoded_buffer = decoded_buffer_recycle;
230 decoded_buffer_recycle = null;
235 if (input_buffer == null)
236 input_buffer = new byte [bufferSize];
238 Array.Clear (input_buffer, 0, bufferSize);
240 if (decoded_buffer == null)
241 decoded_buffer = new char [decoded_buffer_size];
243 Array.Clear (decoded_buffer, 0, decoded_buffer_size);
245 base_stream = stream;
246 this.buffer_size = bufferSize;
247 this.encoding = encoding;
248 decoder = encoding.GetDecoder ();
250 byte [] preamble = encoding.GetPreamble ();
251 do_checks = detectEncodingFromByteOrderMarks ? 1 : 0;
252 do_checks += (preamble.Length == 0) ? 0 : 2;
258 public virtual Stream BaseStream {
264 public virtual Encoding CurrentEncoding {
270 public bool EndOfStream {
271 get { return Peek () < 0; }
274 public override void Close ()
279 protected override void Dispose (bool disposing)
281 if (disposing && base_stream != null && !leave_open)
282 base_stream.Close ();
284 if (input_buffer != null && input_buffer.Length == DefaultBufferSize && input_buffer_recycle == null) {
285 lock (input_buffer_recycle_lock) {
286 if (input_buffer_recycle == null) {
287 input_buffer_recycle = input_buffer;
290 if (decoded_buffer_recycle == null) {
291 decoded_buffer_recycle = decoded_buffer;
297 decoded_buffer = null;
301 base.Dispose (disposing);
305 // Provides auto-detection of the encoding, as well as skipping over
306 // byte marks at the beginning of a stream.
308 int DoChecks (int count)
310 if ((do_checks & 2) == 2){
311 byte [] preamble = encoding.GetPreamble ();
312 int c = preamble.Length;
316 for (i = 0; i < c; i++)
317 if (input_buffer [i] != preamble [i])
325 if ((do_checks & 1) == 1){
329 if (input_buffer [0] == 0xfe && input_buffer [1] == 0xff){
330 this.encoding = Encoding.BigEndianUnicode;
333 if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe && count < 4) {
334 // If we don't have enough bytes we can't check for UTF32, so use Unicode
335 this.encoding = Encoding.Unicode;
342 if (input_buffer [0] == 0xef && input_buffer [1] == 0xbb && input_buffer [2] == 0xbf){
343 this.encoding = Encoding.UTF8Unmarked;
348 if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe && input_buffer [2] != 0) {
349 this.encoding = Encoding.Unicode;
355 if (input_buffer [0] == 0 && input_buffer [1] == 0
356 && input_buffer [2] == 0xfe && input_buffer [3] == 0xff)
358 this.encoding = Encoding.BigEndianUTF32;
362 if (input_buffer [0] == 0xff && input_buffer [1] == 0xfe) {
363 if (input_buffer [2] == 0 && input_buffer[3] == 0) {
364 this.encoding = Encoding.UTF32;
368 this.encoding = Encoding.Unicode;
376 public void DiscardBufferedData ()
380 pos = decoded_count = 0;
382 // Discard internal state of the decoder too.
383 decoder = encoding.GetDecoder ();
386 // the buffer is empty, fill it again
387 // Keep in sync with ReadBufferAsync
392 // keep looping until the decoder gives us some chars
395 var cbEncoded = base_stream.Read (input_buffer, 0, buffer_size);
399 decoded_count = ReadBufferCore (cbEncoded);
400 } while (decoded_count == 0);
402 return decoded_count;
405 int ReadBufferCore (int cbEncoded)
409 mayBlock = cbEncoded < buffer_size;
411 Encoding old = encoding;
412 parse_start = DoChecks (cbEncoded);
413 if (old != encoding){
414 int old_decoded_size = old.GetMaxCharCount (buffer_size) + 1;
415 int new_decoded_size = encoding.GetMaxCharCount (buffer_size) + 1;
416 if (old_decoded_size != new_decoded_size)
417 decoded_buffer = new char [new_decoded_size];
418 decoder = encoding.GetDecoder ();
421 cbEncoded -= parse_start;
426 return decoder.GetChars (input_buffer, parse_start, cbEncoded, decoded_buffer, 0);
431 // http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=96484
433 public override int Peek ()
437 if (pos >= decoded_count && ReadBuffer () == 0)
440 return decoded_buffer [pos];
444 // Used internally by our console, as it previously depended on Peek() being a
445 // routine that would not block.
447 internal bool DataAvailable ()
449 return pos < decoded_count;
452 public override int Read ()
456 if (pos >= decoded_count && ReadBuffer () == 0)
459 return decoded_buffer [pos++];
462 // Keep in sync with ReadAsync
463 public override int Read ([In, Out] char[] buffer, int index, int count)
466 throw new ArgumentNullException ("buffer");
468 throw new ArgumentOutOfRangeException ("index", "< 0");
470 throw new ArgumentOutOfRangeException ("count", "< 0");
471 // re-ordered to avoid possible integer overflow
472 if (index > buffer.Length - count)
473 throw new ArgumentException ("index + count > buffer.Length");
479 if (pos >= decoded_count && ReadBuffer () == 0)
480 return chars_read > 0 ? chars_read : 0;
482 int cch = Math.Min (decoded_count - pos, count);
483 Array.Copy (decoded_buffer, pos, buffer, index, cch);
498 for (; pos < decoded_count; pos++) {
499 c = decoded_buffer [pos];
502 int res = (foundCR) ? (pos - 2) : (pos - 1);
504 res = 0; // if a new buffer starts with a \n and there was a \r at
505 // the end of the previous one, we get here.
508 } else if (foundCR) {
511 return -2; // Need to flush the current buffered line.
512 // This is a \r at the end of the previous decoded buffer that
513 // is not followed by a \n in the current decoded buffer.
517 foundCR = (c == '\r');
523 // Keep in sync with ReadLineAsync
524 public override string ReadLine()
528 if (pos >= decoded_count && ReadBuffer () == 0)
532 int end = FindNextEOL ();
533 if (end < decoded_count && end >= begin)
534 return new string (decoded_buffer, begin, end - begin);
536 return line_builder.ToString (0, line_builder.Length);
538 if (line_builder == null)
539 line_builder = new StringBuilder ();
541 line_builder.Length = 0;
544 if (foundCR) // don't include the trailing CR if present
547 line_builder.Append (decoded_buffer, begin, decoded_count - begin);
548 if (ReadBuffer () == 0) {
549 if (line_builder.Capacity > 32768) {
550 StringBuilder sb = line_builder;
552 return sb.ToString (0, sb.Length);
554 return line_builder.ToString (0, line_builder.Length);
558 end = FindNextEOL ();
559 if (end < decoded_count && end >= begin) {
560 line_builder.Append (decoded_buffer, begin, end - begin);
561 if (line_builder.Capacity > 32768) {
562 StringBuilder sb = line_builder;
564 return sb.ToString (0, sb.Length);
566 return line_builder.ToString (0, line_builder.Length);
570 return line_builder.ToString (0, line_builder.Length);
574 // Keep in sync with ReadToEndAsync
575 public override string ReadToEnd()
579 StringBuilder text = new StringBuilder ();
582 text.Append (decoded_buffer, pos, decoded_count - pos);
583 } while (ReadBuffer () != 0);
585 return text.ToString ();
590 if (base_stream == null)
591 throw new ObjectDisposedException ("StreamReader", "Cannot read from a closed StreamReader");
594 if (async_task != null && !async_task.IsCompleted)
595 throw new InvalidOperationException ();
600 public override int ReadBlock ([In, Out] char[] buffer, int index, int count)
603 throw new ArgumentNullException ("buffer");
605 throw new ArgumentOutOfRangeException ("index", "< 0");
607 throw new ArgumentOutOfRangeException ("count", "< 0");
608 // re-ordered to avoid possible integer overflow
609 if (index > buffer.Length - count)
610 throw new ArgumentException ("index + count > buffer.Length");
614 return base.ReadBlock (buffer, index, count);
617 public override Task<int> ReadAsync (char[] buffer, int index, int count)
620 throw new ArgumentNullException ("buffer");
622 throw new ArgumentOutOfRangeException ("index", "< 0");
624 throw new ArgumentOutOfRangeException ("count", "< 0");
625 // re-ordered to avoid possible integer overflow
626 if (index > buffer.Length - count)
627 throw new ArgumentException ("index + count > buffer.Length");
631 DecoupledTask<int> res;
632 async_task = res = new DecoupledTask<int> (ReadAsyncCore (buffer, index, count));
636 async Task<int> ReadAsyncCore (char[] buffer, int index, int count)
641 if (pos >= decoded_count && await ReadBufferAsync ().ConfigureAwait (false) == 0)
642 return chars_read > 0 ? chars_read : 0;
644 int cch = Math.Min (decoded_count - pos, count);
645 Array.Copy (decoded_buffer, pos, buffer, index, cch);
657 public override Task<int> ReadBlockAsync (char[] buffer, int index, int count)
660 throw new ArgumentNullException ("buffer");
662 throw new ArgumentOutOfRangeException ("index", "< 0");
664 throw new ArgumentOutOfRangeException ("count", "< 0");
665 // re-ordered to avoid possible integer overflow
666 if (index > buffer.Length - count)
667 throw new ArgumentException ("index + count > buffer.Length");
671 DecoupledTask<int> res;
672 async_task = res = new DecoupledTask<int> (ReadAsyncCore (buffer, index, count));
676 public override Task<string> ReadLineAsync ()
680 DecoupledTask<string> res;
681 async_task = res = new DecoupledTask<string> (ReadLineAsyncCore ());
685 async Task<string> ReadLineAsyncCore ()
687 if (pos >= decoded_count && await ReadBufferAsync ().ConfigureAwait (false) == 0)
691 int end = FindNextEOL ();
692 if (end < decoded_count && end >= begin)
693 return new string (decoded_buffer, begin, end - begin);
695 return line_builder.ToString (0, line_builder.Length);
697 if (line_builder == null)
698 line_builder = new StringBuilder ();
700 line_builder.Length = 0;
703 if (foundCR) // don't include the trailing CR if present
706 line_builder.Append (decoded_buffer, begin, decoded_count - begin);
707 if (await ReadBufferAsync ().ConfigureAwait (false) == 0) {
708 if (line_builder.Capacity > 32768) {
709 StringBuilder sb = line_builder;
711 return sb.ToString (0, sb.Length);
713 return line_builder.ToString (0, line_builder.Length);
717 end = FindNextEOL ();
718 if (end < decoded_count && end >= begin) {
719 line_builder.Append (decoded_buffer, begin, end - begin);
720 if (line_builder.Capacity > 32768) {
721 StringBuilder sb = line_builder;
723 return sb.ToString (0, sb.Length);
725 return line_builder.ToString (0, line_builder.Length);
729 return line_builder.ToString (0, line_builder.Length);
733 public override Task<string> ReadToEndAsync ()
737 DecoupledTask<string> res;
738 async_task = res = new DecoupledTask<string> (ReadToEndAsyncCore ());
742 async Task<string> ReadToEndAsyncCore ()
744 StringBuilder text = new StringBuilder ();
747 text.Append (decoded_buffer, pos, decoded_count - pos);
748 } while (await ReadBufferAsync () != 0);
750 return text.ToString ();
753 async Task<int> ReadBufferAsync ()
757 // keep looping until the decoder gives us some chars
760 var cbEncoded = await base_stream.ReadAsync (input_buffer, 0, buffer_size).ConfigureAwait (false);
764 decoded_count = ReadBufferCore (cbEncoded);
765 } while (decoded_count == 0);
767 return decoded_count;