/* Copyright 2010-present 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.Linq;
using MongoDB.Bson.IO;
namespace MongoDB.Bson.Serialization.Serializers
{
///
/// Represents a helper for serializers.
///
public class SerializerHelper
{
// private fields
private readonly long _extraMemberFlag;
private readonly Member[] _members;
private readonly long _requiredMemberFlags;
private readonly BsonTrie _trie;
// constructors
///
/// Initializes a new instance of the class.
///
/// The members.
public SerializerHelper(params Member[] members)
{
if (members == null)
{
throw new ArgumentNullException("members");
}
if (members.Length > 64)
{
throw new ArgumentException("SerializerHelper supports a maximum of 64 members.", "members");
}
_members = members;
_trie = new BsonTrie();
foreach (var member in members)
{
if (!member.IsOptional)
{
_requiredMemberFlags |= member.Flag;
}
if (member.ElementName == "*")
{
_extraMemberFlag = member.Flag;
}
else
{
_trie.Add(member.ElementName, member.Flag);
}
}
}
// public methods
///
/// Deserializes the members.
///
/// The deserialization context.
/// The member handler.
/// The found member flags.
public long DeserializeMembers(BsonDeserializationContext context, Action memberHandler)
{
var reader = context.Reader;
var foundMemberFlags = 0L;
reader.ReadStartDocument();
var trieDecoder = new TrieNameDecoder(_trie);
while (reader.ReadBsonType() != 0)
{
var elementName = reader.ReadName(trieDecoder);
long memberFlag;
if (trieDecoder.Found)
{
memberFlag = trieDecoder.Value;
}
else
{
if (_extraMemberFlag == 0)
{
throw new BsonSerializationException(string.Format(
"Invalid element: '{0}'.", elementName));
}
else
{
memberFlag = _extraMemberFlag;
}
}
memberHandler(elementName, memberFlag);
foundMemberFlags |= memberFlag;
}
reader.ReadEndDocument();
var missingRequiredMemberFlags = _requiredMemberFlags & ~foundMemberFlags;
if (missingRequiredMemberFlags != 0)
{
var missingRequiredMember = FindFirstMissingRequiredMember(missingRequiredMemberFlags);
throw new BsonSerializationException(string.Format(
"Missing element: '{0}'.", missingRequiredMember.ElementName));
}
return foundMemberFlags;
}
// private methods
private Member FindFirstMissingRequiredMember(long missingRequiredMemberFlags)
{
foreach (var member in _members)
{
if ((member.Flag & missingRequiredMemberFlags) != 0)
{
return member;
}
}
throw new BsonInternalException();
}
// nested types
///
/// Represents information about a member.
///
public class Member
{
// private static fields
private static readonly long[] __validFlags = Enumerable.Range(0, 64).Select(i => 1L << i).ToArray();
// private fields
private readonly string _elementName;
private readonly long _flag;
private readonly bool _isOptional;
// constuctors
///
/// Initializes a new instance of the class.
///
/// The name of the element.
/// The flag.
/// Whether the member is optional.
public Member(
string elementName,
long flag,
bool isOptional = false)
{
if (string.IsNullOrEmpty(elementName))
{
throw new ArgumentException(string.Format("Invalid element name: '{0}'.", elementName));
}
if (!__validFlags.Contains(flag))
{
throw new ArgumentException(string.Format("Invalid member flag: {0:x}.", flag));
}
_elementName = elementName;
_flag = flag;
_isOptional = isOptional;
}
// public properties
///
/// Gets the flag.
///
///
/// The flag.
///
public long Flag
{
get { return _flag; }
}
///
/// Gets the name of the element.
///
///
/// The name of the element.
///
public string ElementName
{
get { return _elementName; }
}
///
/// Gets a value indicating whether this member is optional.
///
/// Whether this member is optional.
public bool IsOptional
{
get { return _isOptional; }
}
}
}
}