/* 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.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace MongoDB.Bson.Serialization
{
///
/// Represents a mapping to a delegate and its arguments.
///
public class BsonCreatorMap
{
// private fields
private readonly BsonClassMap _classMap;
private readonly MemberInfo _memberInfo; // null if there is no corresponding constructor or factory method
private readonly Delegate _delegate;
private bool _isFrozen;
private List _arguments; // the members that define the values for the delegate's parameters
// these values are set when Freeze is called
private List _elementNames; // the element names of the serialized arguments
private Dictionary _defaultValues; // not all arguments have default values
// constructors
///
/// Initializes a new instance of the BsonCreatorMap class.
///
/// The class map.
/// The member info (null if none).
/// The delegate.
public BsonCreatorMap(BsonClassMap classMap, MemberInfo memberInfo, Delegate @delegate)
{
if (classMap == null)
{
throw new ArgumentNullException("classMap");
}
if (@delegate == null)
{
throw new ArgumentNullException("delegate");
}
_classMap = classMap;
_memberInfo = memberInfo;
_delegate = @delegate;
}
// public properties
///
/// Gets the arguments.
///
public IEnumerable Arguments
{
get { return _arguments; }
}
///
/// Gets the class map that this creator map belongs to.
///
public BsonClassMap ClassMap
{
get { return _classMap; }
}
///
/// Gets the delegeate
///
public Delegate Delegate
{
get { return _delegate; }
}
///
/// Gets the element names.
///
public IEnumerable ElementNames
{
get
{
if (!_isFrozen) { ThrowNotFrozenException(); }
return _elementNames;
}
}
///
/// Gets the member info (null if none).
///
public MemberInfo MemberInfo
{
get { return _memberInfo; }
}
// public methods
///
/// Freezes the creator map.
///
public void Freeze()
{
if (!_isFrozen)
{
var allMemberMaps = _classMap.AllMemberMaps;
var elementNames = new List();
var defaultValues = new Dictionary();
var expectedArgumentsCount = GetExpectedArgumentsCount();
if (_arguments != null)
{
if (_arguments.Count != expectedArgumentsCount)
{
throw new BsonSerializationException($"Creator map for class {_classMap.ClassType.FullName} has {expectedArgumentsCount} arguments, not {_arguments.Count}.");
}
foreach (var argument in _arguments)
{
var memberMap = allMemberMaps.FirstOrDefault(m => IsSameMember(m.MemberInfo, argument));
if (memberMap == null)
{
var message = string.Format("Member '{0}' is not mapped.", argument.Name);
throw new BsonSerializationException(message);
}
elementNames.Add(memberMap.ElementName);
if (memberMap.IsDefaultValueSpecified)
{
defaultValues.Add(memberMap.ElementName, memberMap.DefaultValue);
}
}
}
else
{
if (expectedArgumentsCount != 0)
{
throw new BsonSerializationException($"Creator map for class {_classMap.ClassType.FullName} has {expectedArgumentsCount} arguments, but none are configured.");
}
}
_elementNames = elementNames;
_defaultValues = defaultValues;
_isFrozen = true;
}
}
///
/// Gets whether there is a default value for a missing element.
///
/// The element name.
/// True if there is a default value for element name; otherwise, false.
public bool HasDefaultValue(string elementName)
{
if (!_isFrozen) { ThrowNotFrozenException(); }
return _defaultValues.ContainsKey(elementName);
}
///
/// Sets the arguments for the creator map.
///
/// The arguments.
/// The creator map.
public BsonCreatorMap SetArguments(IEnumerable arguments)
{
if (arguments == null)
{
throw new ArgumentNullException("arguments");
}
if (_isFrozen) { ThrowFrozenException(); }
var argumentsList = arguments.ToList(); // only enumerate once
var expectedArgumentsCount = GetExpectedArgumentsCount();
if (argumentsList.Count != expectedArgumentsCount)
{
throw new ArgumentException($"Creator map for class {_classMap.ClassType.FullName} has {expectedArgumentsCount} arguments, not {argumentsList.Count}.", nameof(arguments));
}
_arguments = argumentsList;
return this;
}
///
/// Sets the arguments for the creator map.
///
/// The argument names.
/// The creator map.
public BsonCreatorMap SetArguments(IEnumerable argumentNames)
{
if (argumentNames == null)
{
throw new ArgumentNullException("argumentNames");
}
if (_isFrozen) { ThrowFrozenException(); }
var classTypeInfo = _classMap.ClassType.GetTypeInfo();
var arguments = new List();
foreach (var argumentName in argumentNames)
{
var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var memberInfos = classTypeInfo.GetMembers(bindingFlags)
.Where(m => m.Name == argumentName && (m is FieldInfo || m is PropertyInfo))
.ToArray();
if (memberInfos.Length == 0)
{
var message = string.Format("Class '{0}' does not have a member named '{1}'.", _classMap.ClassType.FullName, argumentName);
throw new BsonSerializationException(message);
}
else if (memberInfos.Length > 1)
{
var message = string.Format("Class '{0}' has more than one member named '{1}'.", _classMap.ClassType.FullName, argumentName);
throw new BsonSerializationException(message);
}
arguments.Add(memberInfos[0]);
}
SetArguments(arguments);
return this;
}
// internal methods
internal object CreateInstance(Dictionary values)
{
var arguments = new List