/* Copyright 2010-2015 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 mapping from a set of UTF8 encoded strings to a set of elementName/value pairs, implemented as a trie. /// /// The type of the BsonTrie values. public class BsonTrie { // private fields private readonly BsonTrieNode _root; // constructors /// /// Initializes a new instance of the BsonTrie class. /// public BsonTrie() { _root = new BsonTrieNode(0); } // public properties /// /// Gets the root node. /// public BsonTrieNode Root { get { return _root; } } // public methods /// /// Adds the specified elementName (after encoding as a UTF8 byte sequence) and value to the trie. /// /// The element name to add. /// The value to add. The value can be null for reference types. public void Add(string elementName, TValue value) { var utf8 = Utf8Encodings.Strict.GetBytes(elementName); var node = _root; foreach (var keyByte in utf8) { var child = node.GetChild(keyByte); if (child == null) { child = new BsonTrieNode(keyByte); node.AddChild(child); } node = child; } node.SetValue(elementName, value); } /// /// Gets the node associated with the specified element name. /// /// The element name. /// /// When this method returns, contains the node associated with the specified element name, if the key is found; /// otherwise, null. This parameter is passed unitialized. /// /// True if the node was found; otherwise, false. public bool TryGetNode(ArraySegment utf8, out BsonTrieNode node) { node = _root; for (var i = 0; node != null && i < utf8.Count; i++) { var keyByte = utf8.Array[utf8.Offset + i]; node = node.GetChild(keyByte); } return node != null; } /// /// Tries to get the node associated with a name read from a stream. /// /// The stream. /// The node. /// /// True if the node was found. /// If the node was found the stream is advanced over the name, otherwise /// the stream is repositioned to the beginning of the name. /// public bool TryGetNode(BsonStream stream, out BsonTrieNode node) { var position = stream.Position; var utf8 = stream.ReadCStringBytes(); if (TryGetNode(utf8, out node)) { return true; } stream.Position = position; return false; } /// /// Gets the value associated with the specified element name. /// /// The element name. /// /// When this method returns, contains the value associated with the specified element name, if the key is found; /// otherwise, the default value for the type of the value parameter. This parameter is passed unitialized. /// /// True if the value was found; otherwise, false. public bool TryGetValue(ArraySegment utf8, out TValue value) { BsonTrieNode node; if (TryGetNode(utf8, out node) && node.HasValue) { value = node.Value; return true; } value = default(TValue); return false; } /// /// Gets the value associated with the specified element name. /// /// The element name. /// /// When this method returns, contains the value associated with the specified element name, if the key is found; /// otherwise, the default value for the type of the value parameter. This parameter is passed unitialized. /// /// True if the value was found; otherwise, false. public bool TryGetValue(string elementName, out TValue value) { var bytes = Utf8Encodings.Strict.GetBytes(elementName); var utf8 = new ArraySegment(bytes, 0, bytes.Length); return TryGetValue(utf8, out value); } } /// /// Represents a node in a BsonTrie. /// /// The type of the BsonTrie values. public sealed class BsonTrieNode { // private fields private readonly byte _keyByte; private string _elementName; private TValue _value; private BsonTrieNode _onlyChild; // used when there is only one child private BsonTrieNode[] _children; // used when there are two or more children private byte[] _childrenIndexes; // maps key bytes into indexes into the _children array private byte _minChildKeyByte; // key byte value of first element in _childrenIndexes // constructors internal BsonTrieNode(byte keyByte) { _keyByte = keyByte; } /// /// Gets whether this node has a value. /// public bool HasValue { get { return _elementName != null; } } /// /// Gets the element name for this node. /// public string ElementName { get { if (_elementName == null) { throw new InvalidOperationException("BsonTrieNode doesn't have a value."); } return _elementName; } } /// /// Gets the value for this node. /// public TValue Value { get { if (_elementName == null) { throw new InvalidOperationException("BsonTrieNode doesn't have a value."); } return _value; } } // public methods /// /// Gets the child of this node for a given key byte. /// /// The key byte. /// The child node if it exists; otherwise, null. public BsonTrieNode GetChild(byte keyByte) { if (_onlyChild != null) { // optimization for nodes that have only one child if (_onlyChild._keyByte == keyByte) { return _onlyChild; } } else if (_children != null) { var index = (uint)((int)keyByte - _minChildKeyByte); if (index < _childrenIndexes.Length) { index = _childrenIndexes[index]; if (index < _children.Length) { return _children[index]; } } } return null; } // internal methods internal void AddChild(BsonTrieNode child) { if (GetChild(child._keyByte) != null) { throw new ArgumentException("BsonTrieNode already contains a child with the same keyByte."); } if (_children != null) { // add a new child to the existing _children var children = new BsonTrieNode[_children.Length + 1]; Array.Copy(_children, children, _children.Length); children[children.Length - 1] = child; var childrenIndexes = _childrenIndexes; var minChildKeyByte = _minChildKeyByte; var maxChildKeyByte = _minChildKeyByte + _childrenIndexes.Length - 1; // if new keyByte doesn't fall within existing min/max range expand the range if (child._keyByte < minChildKeyByte) { // grow the indexes on the min side minChildKeyByte = child._keyByte; childrenIndexes = new byte[maxChildKeyByte - minChildKeyByte + 1]; var sizeDelta = childrenIndexes.Length - _childrenIndexes.Length; for (var i = 0; i < sizeDelta; i++) { childrenIndexes[i] = 255; } Array.Copy(_childrenIndexes, 0, childrenIndexes, sizeDelta, _childrenIndexes.Length); } else if (child._keyByte > maxChildKeyByte) { // grow the indexes on the max side maxChildKeyByte = child._keyByte; childrenIndexes = new byte[maxChildKeyByte - minChildKeyByte + 1]; Array.Copy(_childrenIndexes, 0, childrenIndexes, 0, _childrenIndexes.Length); for (var i = _childrenIndexes.Length; i < childrenIndexes.Length; i++) { childrenIndexes[i] = 255; } } childrenIndexes[child._keyByte - minChildKeyByte] = (byte)(children.Length - 1); _children = children; _childrenIndexes = childrenIndexes; _minChildKeyByte = minChildKeyByte; } else if (_onlyChild != null) { // switch from having an _onlyChild to having two _children var children = new BsonTrieNode[2]; children[0] = _onlyChild; children[1] = child; var minChildKeyByte = _onlyChild._keyByte; var maxChildKeyByte = child._keyByte; if (minChildKeyByte > maxChildKeyByte) { minChildKeyByte = child._keyByte; maxChildKeyByte = _onlyChild._keyByte; } var childrenIndexes = new byte[maxChildKeyByte - minChildKeyByte + 1]; for (var i = 0; i < childrenIndexes.Length; i++) { childrenIndexes[i] = 255; } childrenIndexes[_onlyChild._keyByte - minChildKeyByte] = 0; childrenIndexes[child._keyByte - minChildKeyByte] = 1; _onlyChild = null; _children = children; _childrenIndexes = childrenIndexes; _minChildKeyByte = minChildKeyByte; } else { _onlyChild = child; } } internal void SetValue(string elementName, TValue value) { if (elementName == null) { throw new ArgumentNullException("elementName"); } if (_elementName != null) { throw new InvalidOperationException("BsonTrieNode already has a value."); } _elementName = elementName; _value = value; } } }