Skip to content

Commit 61f0fbf

Browse files
author
rstam
committed
CSHARP-933: Add support to JsonReader to read from a TextReader.
1 parent e0d5442 commit 61f0fbf

File tree

12 files changed

+331
-58
lines changed

12 files changed

+331
-58
lines changed

src/MongoDB.Bson.Tests/IO/BsonBinaryReaderTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,34 @@ public void TestBsonAwesome()
6666
}
6767
}
6868

69+
[Test]
70+
public void TestIsAtEndOfFileWithTwoDocuments()
71+
{
72+
var expected = new BsonDocument("x", 1);
73+
74+
byte[] bson;
75+
using (var stream = new MemoryStream())
76+
using (var writer = new BsonBinaryWriter(stream))
77+
{
78+
BsonSerializer.Serialize(writer, expected);
79+
BsonSerializer.Serialize(writer, expected);
80+
bson = stream.ToArray();
81+
}
82+
83+
using (var stream = new MemoryStream(bson))
84+
using (var reader = new BsonBinaryReader(stream))
85+
{
86+
var count = 0;
87+
while (!reader.IsAtEndOfFile())
88+
{
89+
var document = BsonSerializer.Deserialize<BsonDocument>(reader);
90+
Assert.AreEqual(expected, document);
91+
count++;
92+
}
93+
Assert.AreEqual(2, count);
94+
}
95+
}
96+
6997
[Test]
7098
public void TestReadRawBsonArray()
7199
{
@@ -96,6 +124,7 @@ public void TestReadRawBsonDocument()
96124
}
97125
}
98126

127+
// private methods
99128
private static string __hexDigits = "0123456789abcdef";
100129

101130
private byte[] DecodeByteString(string byteString)

src/MongoDB.Bson.Tests/IO/BsonDocumentReaderTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,24 @@ public void TestGuid()
146146
}
147147
}
148148

149+
[Test]
150+
public void TestIsAtEndOfFile()
151+
{
152+
var expected = new BsonDocument("x", 1);
153+
154+
using (var reader = new BsonDocumentReader(expected))
155+
{
156+
var count = 0;
157+
while (!reader.IsAtEndOfFile())
158+
{
159+
var document = BsonSerializer.Deserialize<BsonDocument>(reader);
160+
Assert.AreEqual(expected, document);
161+
count++;
162+
}
163+
Assert.AreEqual(1, count);
164+
}
165+
}
166+
149167
[Test]
150168
public void TestMaxKey()
151169
{

src/MongoDB.Bson.Tests/IO/JsonReaderTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,44 @@ public void TestInt64NumberLong()
418418
Assert.AreEqual(json, BsonSerializer.Deserialize<long>(json).ToJson());
419419
}
420420

421+
[Test]
422+
public void TestIsAtEndOfFileWithTwoArrays()
423+
{
424+
var json = "[1,2][1,2]";
425+
426+
using (var jsonReader = new JsonReader(json))
427+
{
428+
var count = 0;
429+
while (!jsonReader.IsAtEndOfFile())
430+
{
431+
var array = BsonSerializer.Deserialize<BsonArray>(jsonReader);
432+
var expected = new BsonArray { 1, 2 };
433+
Assert.AreEqual(expected, array);
434+
count += 1;
435+
}
436+
Assert.AreEqual(2, count);
437+
}
438+
}
439+
440+
[Test]
441+
public void TestIsAtEndOfFileWithTwoDocuments()
442+
{
443+
var json = "{x:1}{x:1}";
444+
445+
using (var jsonReader = new JsonReader(json))
446+
{
447+
var count = 0;
448+
while (!jsonReader.IsAtEndOfFile())
449+
{
450+
var document = BsonSerializer.Deserialize<BsonDocument>(jsonReader);
451+
var expected = new BsonDocument("x", 1);
452+
Assert.AreEqual(expected, document);
453+
count += 1;
454+
}
455+
Assert.AreEqual(2, count);
456+
}
457+
}
458+
421459
[Test]
422460
public void TestJavaScript()
423461
{

src/MongoDB.Bson/IO/BsonBinaryReader.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,27 @@ public override BsonReaderBookmark GetBookmark()
8181
return new BsonBinaryReaderBookmark(State, CurrentBsonType, CurrentName, _context, (int)_streamReader.Position);
8282
}
8383

84+
/// <summary>
85+
/// Determines whether this reader is at end of file.
86+
/// </summary>
87+
/// <returns>
88+
/// Whether this reader is at end of file.
89+
/// </returns>
90+
public override bool IsAtEndOfFile()
91+
{
92+
var stream = _streamReader.BaseStream;
93+
var c = stream.ReadByte();
94+
if (c == -1)
95+
{
96+
return true;
97+
}
98+
else
99+
{
100+
stream.Seek(-1, SeekOrigin.Current);
101+
return false;
102+
}
103+
}
104+
84105
/// <summary>
85106
/// Reads BSON binary data from the reader.
86107
/// </summary>

src/MongoDB.Bson/IO/BsonDocumentReader.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,17 @@ public override BsonReaderBookmark GetBookmark()
7575
return new BsonDocumentReaderBookmark(State, CurrentBsonType, CurrentName, _context, _currentValue);
7676
}
7777

78+
/// <summary>
79+
/// Determines whether this reader is at end of file.
80+
/// </summary>
81+
/// <returns>
82+
/// Whether this reader is at end of file.
83+
/// </returns>
84+
public override bool IsAtEndOfFile()
85+
{
86+
return State == BsonReaderState.Done;
87+
}
88+
7889
/// <summary>
7990
/// Reads BSON binary data from the reader.
8091
/// </summary>

src/MongoDB.Bson/IO/BsonReader.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ public BsonType GetCurrentBsonType()
193193
return _currentBsonType;
194194
}
195195

196+
/// <summary>
197+
/// Determines whether this reader is at end of file.
198+
/// </summary>
199+
/// <returns>
200+
/// Whether this reader is at end of file.
201+
/// </returns>
202+
public abstract bool IsAtEndOfFile();
203+
196204
/// <summary>
197205
/// Reads BSON binary data from the reader.
198206
/// </summary>

src/MongoDB.Bson/IO/JsonBuffer.cs

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,87 +13,192 @@
1313
* limitations under the License.
1414
*/
1515

16+
using System;
17+
using System.IO;
18+
using System.Text;
19+
1620
namespace MongoDB.Bson.IO
1721
{
1822
/// <summary>
19-
/// This class represents a JSON string buffer.
23+
/// Represents a wrapper around a TextReader to provide some buffering functionality.
2024
/// </summary>
21-
public class JsonBuffer
25+
internal class JsonBuffer
2226
{
2327
// private fields
24-
private string _buffer;
28+
private readonly StringBuilder _buffer;
2529
private int _position;
30+
private readonly TextReader _reader;
2631

2732
// constructors
2833
/// <summary>
29-
/// Initializes a new instance of the JsonBuffer class.
34+
/// Initializes a new instance of the <see cref="JsonBuffer"/> class.
3035
/// </summary>
31-
/// <param name="buffer">The string.</param>
32-
public JsonBuffer(string buffer)
36+
/// <param name="json">The json.</param>
37+
public JsonBuffer(string json)
3338
{
34-
_buffer = buffer;
35-
_position = 0;
39+
if (json == null)
40+
{
41+
throw new ArgumentNullException("json");
42+
}
43+
_buffer = new StringBuilder(json);
3644
}
3745

38-
// internal properties
3946
/// <summary>
40-
/// Gets the length of the JSON string.
47+
/// Initializes a new instance of the <see cref="JsonBuffer" /> class.
4148
/// </summary>
42-
public int Length
49+
/// <param name="reader">The reader.</param>
50+
public JsonBuffer(TextReader reader)
4351
{
44-
get { return _buffer.Length; }
52+
if (reader == null)
53+
{
54+
throw new ArgumentNullException("reader");
55+
}
56+
_buffer = new StringBuilder(256); // start out with a reasonable initial capacity
57+
_reader = reader;
4558
}
4659

60+
// public properties
4761
/// <summary>
4862
/// Gets or sets the current position.
4963
/// </summary>
5064
public int Position
5165
{
5266
get { return _position; }
53-
set { _position = value; }
67+
set
68+
{
69+
if (value < 0 || value > _buffer.Length)
70+
{
71+
var message = string.Format("Invalid position: {0}.", value);
72+
throw new ArgumentOutOfRangeException("value", message);
73+
}
74+
_position = value;
75+
}
5476
}
5577

5678
// public methods
5779
/// <summary>
58-
/// Reads a character from the buffer.
80+
/// Gets a snippet of a maximum length from the buffer (usually to include in an error message).
5981
/// </summary>
60-
/// <returns>The next character (or -1 if at the end of the buffer).</returns>
61-
public int Read()
82+
/// <param name="start">The start.</param>
83+
/// <param name="maxLength">The maximum length.</param>
84+
/// <returns>The snippet.</returns>
85+
public string GetSnippet(int start, int maxLength)
6286
{
63-
return (_position >= _buffer.Length) ? -1 : _buffer[_position++];
87+
if (start < 0)
88+
{
89+
throw new ArgumentOutOfRangeException("start", "Start cannot be negative.");
90+
}
91+
if (maxLength < 0)
92+
{
93+
throw new ArgumentOutOfRangeException("maxLength", "MaxLength cannot be negative.");
94+
}
95+
if (start > _position)
96+
{
97+
throw new ArgumentOutOfRangeException("start", "Start is beyond current position.");
98+
}
99+
var availableCount = _position - start;
100+
var count = Math.Min(availableCount, maxLength);
101+
return _buffer.ToString(start, count);
64102
}
65103

66104
/// <summary>
67-
/// Reads a substring from the buffer.
105+
/// Gets a substring from the buffer.
68106
/// </summary>
69-
/// <param name="start">The zero based index of the start of the substring.</param>
107+
/// <param name="start">The start.</param>
108+
/// <param name="count">The count.</param>
70109
/// <returns>The substring.</returns>
71-
public string Substring(int start)
110+
public string GetSubstring(int start, int count)
111+
{
112+
if (start < 0)
113+
{
114+
throw new ArgumentOutOfRangeException("start", "Start cannot be negative.");
115+
}
116+
if (count < 0)
117+
{
118+
throw new ArgumentOutOfRangeException("count", "Count cannot be negative.");
119+
}
120+
if (start > _position)
121+
{
122+
throw new ArgumentOutOfRangeException("start", "Start is beyond current position.");
123+
}
124+
if (start + count > _position)
125+
{
126+
throw new ArgumentOutOfRangeException("start", "End of substring is beyond current position.");
127+
}
128+
return _buffer.ToString(start, count);
129+
}
130+
131+
/// <summary>
132+
/// Reads the next character from the text reader and advances the character position by one character.
133+
/// </summary>
134+
/// <returns>
135+
/// The next character from the text reader, or -1 if no more characters are available. The default implementation returns -1.
136+
/// </returns>
137+
public int Read()
72138
{
73-
return _buffer.Substring(start);
139+
ReadMoreIfAtEndOfBuffer();
140+
return _position >= _buffer.Length ? -1 : _buffer[_position++];
74141
}
75142

76143
/// <summary>
77-
/// Reads a substring from the buffer.
144+
/// Resets the buffer (clears everything up to the current position).
78145
/// </summary>
79-
/// <param name="start">The zero based index of the start of the substring.</param>
80-
/// <param name="count">The number of characters in the substring.</param>
81-
/// <returns>The substring.</returns>
82-
public string Substring(int start, int count)
146+
public void ResetBuffer()
83147
{
84-
return _buffer.Substring(start, count);
148+
// only trim the buffer if enough space will be reclaimed to make it worthwhile
149+
var minimumTrimCount = 256; // TODO: make configurable?
150+
if (_position >= minimumTrimCount)
151+
{
152+
_buffer.Remove(0, _position);
153+
_position = 0;
154+
}
85155
}
86156

87157
/// <summary>
88-
/// Returns one character to the buffer (if the character matches the one at the current position the current position is moved back by one).
158+
/// Unreads one character (moving the current Position back one position).
89159
/// </summary>
90-
/// <param name="c">The character to return.</param>
160+
/// <param name="c">The character.</param>
91161
public void UnRead(int c)
92162
{
93-
if (c != -1 && _buffer[_position - 1] == c)
163+
if (_position == 0)
94164
{
165+
throw new InvalidOperationException("Unread called when nothing has been read.");
166+
}
167+
168+
if (c == -1)
169+
{
170+
if (_position != _buffer.Length)
171+
{
172+
throw new InvalidOperationException("Unread called with -1 when position is not at the end of the buffer.");
173+
}
174+
}
175+
else
176+
{
177+
if (_buffer[_position - 1] != c)
178+
{
179+
throw new InvalidOperationException("Unread called with a character that does not match what is in the buffer.");
180+
}
95181
_position -= 1;
96182
}
97183
}
184+
185+
// private methods
186+
private void ReadMoreIfAtEndOfBuffer()
187+
{
188+
if (_position >= _buffer.Length)
189+
{
190+
if (_reader != null)
191+
{
192+
var blockSize = 1024; // TODO: make configurable?
193+
var block = new char[blockSize];
194+
var actualCount = _reader.ReadBlock(block, 0, blockSize);
195+
196+
if (actualCount > 0)
197+
{
198+
_buffer.Append(block, 0, actualCount);
199+
}
200+
}
201+
}
202+
}
98203
}
99204
}

0 commit comments

Comments
 (0)