/* Copyright 2010-2014 MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.IO;
using System.Text;
namespace MongoDB.Bson.IO
{
///
/// Represents a wrapper around a TextReader to provide some buffering functionality.
///
internal class JsonBuffer
{
// private fields
private readonly StringBuilder _buffer;
private int _position;
private readonly TextReader _reader;
// constructors
///
/// Initializes a new instance of the class.
///
/// The json.
public JsonBuffer(string json)
{
if (json == null)
{
throw new ArgumentNullException("json");
}
_buffer = new StringBuilder(json);
}
///
/// Initializes a new instance of the class.
///
/// The reader.
public JsonBuffer(TextReader reader)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
_buffer = new StringBuilder(256); // start out with a reasonable initial capacity
_reader = reader;
}
// public properties
///
/// Gets or sets the current position.
///
public int Position
{
get { return _position; }
set
{
if (value < 0 || value > _buffer.Length)
{
var message = string.Format("Invalid position: {0}.", value);
throw new ArgumentOutOfRangeException("value", message);
}
_position = value;
}
}
// public methods
///
/// Gets a snippet of a maximum length from the buffer (usually to include in an error message).
///
/// The start.
/// The maximum length.
/// The snippet.
public string GetSnippet(int start, int maxLength)
{
if (start < 0)
{
throw new ArgumentOutOfRangeException("start", "Start cannot be negative.");
}
if (maxLength < 0)
{
throw new ArgumentOutOfRangeException("maxLength", "MaxLength cannot be negative.");
}
if (start > _position)
{
throw new ArgumentOutOfRangeException("start", "Start is beyond current position.");
}
var availableCount = _position - start;
var count = Math.Min(availableCount, maxLength);
return _buffer.ToString(start, count);
}
///
/// Gets a substring from the buffer.
///
/// The start.
/// The count.
/// The substring.
public string GetSubstring(int start, int count)
{
if (start < 0)
{
throw new ArgumentOutOfRangeException("start", "Start cannot be negative.");
}
if (count < 0)
{
throw new ArgumentOutOfRangeException("count", "Count cannot be negative.");
}
if (start > _position)
{
throw new ArgumentOutOfRangeException("start", "Start is beyond current position.");
}
if (start + count > _position)
{
throw new ArgumentOutOfRangeException("start", "End of substring is beyond current position.");
}
return _buffer.ToString(start, count);
}
///
/// Reads the next character from the text reader and advances the character position by one character.
///
///
/// The next character from the text reader, or -1 if no more characters are available. The default implementation returns -1.
///
public int Read()
{
ReadMoreIfAtEndOfBuffer();
return _position >= _buffer.Length ? -1 : _buffer[_position++];
}
///
/// Resets the buffer (clears everything up to the current position).
///
public void ResetBuffer()
{
// only trim the buffer if enough space will be reclaimed to make it worthwhile
var minimumTrimCount = 256; // TODO: make configurable?
if (_position >= minimumTrimCount)
{
_buffer.Remove(0, _position);
_position = 0;
}
}
///
/// Unreads one character (moving the current Position back one position).
///
/// The character.
public void UnRead(int c)
{
if (_position == 0)
{
throw new InvalidOperationException("Unread called when nothing has been read.");
}
if (c == -1)
{
if (_position != _buffer.Length)
{
throw new InvalidOperationException("Unread called with -1 when position is not at the end of the buffer.");
}
}
else
{
if (_buffer[_position - 1] != c)
{
throw new InvalidOperationException("Unread called with a character that does not match what is in the buffer.");
}
_position -= 1;
}
}
// private methods
private void ReadMoreIfAtEndOfBuffer()
{
if (_position >= _buffer.Length)
{
if (_reader != null)
{
var blockSize = 1024; // TODO: make configurable?
var block = new char[blockSize];
var actualCount = _reader.ReadBlock(block, 0, blockSize);
if (actualCount > 0)
{
_buffer.Append(block, 0, actualCount);
}
}
}
}
}
}