Skip to content
Browse files

CSHARP-476: Added support for calling constructors, factory methods o…

…r arbitrary code to instantiate an object during deserialization.
  • Loading branch information...
1 parent a5d7325 commit e0f958edea7be79a4831b3cf10e990b3e3149d04 rstam committed Jan 15, 2013
View
10 MongoDB.Bson/MongoDB.Bson.csproj
@@ -94,9 +94,11 @@
<Compile Include="IO\JsonReaderSettings.cs" />
<Compile Include="ObjectModel\ICustomBsonTypeMapper.cs" />
<Compile Include="ObjectModel\BsonTypeMapperOptions.cs" />
+ <Compile Include="Serialization\Attributes\BsonConstructorAttribute.cs" />
<Compile Include="Serialization\Attributes\BsonDateTimeOptionsAttribute.cs" />
<Compile Include="Serialization\Attributes\BsonDictionaryOptionsAttribute.cs" />
<Compile Include="Serialization\Attributes\BsonExtraElementsAttribute.cs" />
+ <Compile Include="Serialization\Attributes\BsonFactoryMethodAttribute.cs" />
<Compile Include="Serialization\Attributes\BsonIgnoreIfDefaultAttribute.cs" />
<Compile Include="Serialization\Attributes\BsonMemberMapAttributeUsageAttribute.cs" />
<Compile Include="Serialization\Attributes\BsonNoIdAttribute.cs" />
@@ -107,10 +109,13 @@
<Compile Include="Serialization\Attributes\IBsonMemberMapAttribute.cs" />
<Compile Include="Serialization\Attributes\IBsonClassMapAttribute.cs" />
<Compile Include="Serialization\Attributes\IBsonPostProcessingAttribute.cs" />
+ <Compile Include="Serialization\Attributes\IBsonCreatorMapAttribute.cs" />
<Compile Include="Serialization\BsonClassMapSerializationProvider.cs" />
+ <Compile Include="Serialization\BsonCreatorMap.cs" />
<Compile Include="Serialization\BsonDocumentBackedClass.cs" />
<Compile Include="Serialization\Conventions\AttributeConventionPack.cs" />
<Compile Include="Serialization\Conventions\CamelCaseElementNameConvention.cs" />
+ <Compile Include="Serialization\Conventions\NamedParameterCreatorMapConvention.cs" />
<Compile Include="Serialization\Conventions\ConventionBase.cs" />
<Compile Include="Serialization\Conventions\ConventionPack.cs" />
<Compile Include="Serialization\Conventions\ConventionRegistry.cs" />
@@ -121,6 +126,7 @@
<Compile Include="Serialization\Conventions\DelegatePostProcessingConvention.cs" />
<Compile Include="Serialization\Conventions\HierarchicalDiscriminatorConvention.cs" />
<Compile Include="Serialization\Conventions\IClassMapConvention.cs" />
+ <Compile Include="Serialization\Conventions\ICreatorMapConvention.cs" />
<Compile Include="Serialization\Conventions\IConvention.cs" />
<Compile Include="Serialization\Conventions\IConventionPack.cs" />
<Compile Include="Serialization\Conventions\IgnoreExtraElementsConvention.cs" />
@@ -153,6 +159,9 @@
<Compile Include="Serialization\Conventions\ScalarDiscriminatorConvention.cs" />
<Compile Include="Serialization\Conventions\StandardDiscriminatorConvention.cs" />
<Compile Include="Serialization\Conventions\StringObjectIdIdGeneratorConvention.cs" />
+ <Compile Include="Serialization\CreatorMapDelegateCompiler.cs" />
+ <Compile Include="Serialization\ExpressionVisitor.cs" />
+ <Compile Include="Serialization\ICreatorSelector.cs" />
<Compile Include="Serialization\IdGenerators\BsonBinaryDataGuidGenerator.cs" />
<Compile Include="Serialization\IdGenerators\BsonObjectIdGenerator.cs" />
<Compile Include="Serialization\IdGenerators\CombGuidGenerator.cs" />
@@ -161,6 +170,7 @@
<Compile Include="Serialization\IdGenerators\ObjectIdGenerator.cs" />
<Compile Include="Serialization\IdGenerators\StringObjectIdGenerator.cs" />
<Compile Include="Serialization\IdGenerators\ZeroIdChecker.cs" />
+ <Compile Include="Serialization\MostArgumentsCreatorSelector.cs" />
<Compile Include="Serialization\Serializers\BitArraySerializer.cs" />
<Compile Include="Serialization\Serializers\BitmapSerializer.cs" />
<Compile Include="Serialization\Serializers\BooleanSerializer.cs" />
View
67 MongoDB.Bson/Serialization/Attributes/BsonConstructorAttribute.cs
@@ -0,0 +1,67 @@
+/* Copyright 2010-2013 10gen 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;
+
+namespace MongoDB.Bson.Serialization.Attributes
+{
+ /// <summary>
+ /// Specifies that this constructor should be used for creator-based deserialization.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Constructor)]
+ public class BsonConstructorAttribute : Attribute, IBsonCreatorMapAttribute
+ {
+ // private fields
+ private string[] _argumentNames;
+
+ // constructors
+ /// <summary>
+ /// Initializes a new instance of the BsonConstructorAttribute class.
+ /// </summary>
+ public BsonConstructorAttribute()
+ { }
+
+ /// <summary>
+ /// Initializes a new instance of the BsonConstructorAttribute class.
+ /// </summary>
+ /// <param name="argumentNames">The names of the members that the creator argument values come from.</param>
+ public BsonConstructorAttribute(params string[] argumentNames)
+ {
+ _argumentNames = argumentNames;
+ }
+
+ // public properties
+ /// <summary>
+ /// Gets the names of the members that the creator arguments values come from.
+ /// </summary>
+ public string[] ArgumentNames
+ {
+ get { return _argumentNames; }
+ }
+
+ // public methods
+ /// <summary>
+ /// Applies a modification to the creator map.
+ /// </summary>
+ /// <param name="creatorMap">The creator map.</param>
+ public void Apply(BsonCreatorMap creatorMap)
+ {
+ if (_argumentNames != null)
+ {
+ creatorMap.SetArguments(_argumentNames);
+ }
+ }
+ }
+}
View
67 MongoDB.Bson/Serialization/Attributes/BsonFactoryMethodAttribute.cs
@@ -0,0 +1,67 @@
+/* Copyright 2010-2013 10gen 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;
+
+namespace MongoDB.Bson.Serialization.Attributes
+{
+ /// <summary>
+ /// Specifies that this factory method should be used for creator-based deserialization.
+ /// </summary>
+ [AttributeUsage(AttributeTargets.Method)]
+ public class BsonFactoryMethodAttribute : Attribute, IBsonCreatorMapAttribute
+ {
+ // private fields
+ private string[] _argumentNames;
+
+ // constructors
+ /// <summary>
+ /// Initializes a new instance of the BsonFactoryMethodAttribute class.
+ /// </summary>
+ public BsonFactoryMethodAttribute()
+ { }
+
+ /// <summary>
+ /// Initializes a new instance of the BsonFactoryMethodAttribute class.
+ /// </summary>
+ /// <param name="argumentNames">The names of the members that the creator argument values come from.</param>
+ public BsonFactoryMethodAttribute(params string[] argumentNames)
+ {
+ _argumentNames = argumentNames;
+ }
+
+ // public properties
+ /// <summary>
+ /// Gets the names of the members that the creator arguments values come from.
+ /// </summary>
+ public string[] ArgumentNames
+ {
+ get { return _argumentNames; }
+ }
+
+ // public methods
+ /// <summary>
+ /// Applies a modification to the creator map.
+ /// </summary>
+ /// <param name="creatorMap">The creator map.</param>
+ public void Apply(BsonCreatorMap creatorMap)
+ {
+ if (_argumentNames != null)
+ {
+ creatorMap.SetArguments(_argumentNames);
+ }
+ }
+ }
+}
View
29 MongoDB.Bson/Serialization/Attributes/IBsonCreatorMapAttribute.cs
@@ -0,0 +1,29 @@
+/* Copyright 2010-2013 10gen 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.
+*/
+
+namespace MongoDB.Bson.Serialization
+{
+ /// <summary>
+ /// Represents an attribute used to modify a creator map.
+ /// </summary>
+ public interface IBsonCreatorMapAttribute
+ {
+ /// <summary>
+ /// Applies the attribute to the creator map.
+ /// </summary>
+ /// <param name="creatorMap">The creator map.</param>
+ void Apply(BsonCreatorMap creatorMap);
+ }
+}
View
192 MongoDB.Bson/Serialization/BsonClassMap.cs
@@ -42,6 +42,7 @@ public abstract class BsonClassMap
private BsonClassMap _baseClassMap; // null for class object and interfaces
private Type _classType;
private volatile IDiscriminatorConvention _cachedDiscriminatorConvention;
+ private readonly List<BsonCreatorMap> _creatorMaps;
private Func<object> _creator;
private IConventionPack _conventionPack;
private string _discriminator;
@@ -68,6 +69,7 @@ public abstract class BsonClassMap
protected BsonClassMap(Type classType)
{
_classType = classType;
+ _creatorMaps = new List<BsonCreatorMap>();
_conventionPack = ConventionRegistry.Lookup(classType);
_isAnonymous = IsAnonymousType(classType);
_allMemberMaps = new List<BsonMemberMap>();
@@ -104,6 +106,14 @@ public Type ClassType
}
/// <summary>
+ /// Gets the constructor maps.
+ /// </summary>
+ public IEnumerable<BsonCreatorMap> CreatorMaps
+ {
+ get { return _creatorMaps; }
+ }
+
+ /// <summary>
/// Gets the conventions used for auto mapping.
/// </summary>
public IConventionPack ConventionPack
@@ -162,6 +172,14 @@ public BsonMemberMap ExtraElementsMemberMap
}
/// <summary>
+ /// Gets whether this class map has any creator maps.
+ /// </summary>
+ public bool HasCreatorMaps
+ {
+ get { return _creatorMaps.Count > 0; }
+ }
+
+ /// <summary>
/// Gets whether this class has a root class ancestor.
/// </summary>
public bool HasRootClass
@@ -553,6 +571,11 @@ public BsonClassMap Freeze()
// because we might get back to this same classMap while processing knownTypes
_frozen = true;
+ foreach (var creatorMap in _creatorMaps)
+ {
+ creatorMap.Freeze();
+ }
+
// use a queue to postpone processing of known types until we get back to the first level call to Freeze
// this avoids infinite recursion when going back down the inheritance tree while processing known types
foreach (var knownType in _knownTypes)
@@ -622,6 +645,74 @@ public BsonMemberMap GetMemberMapForElement(string elementName)
}
/// <summary>
+ /// Creates a creator map for a constructor and adds it to the class map.
+ /// </summary>
+ /// <param name="constructorInfo">The constructor info.</param>
+ /// <returns>The creator map (so method calls can be chained).</returns>
+ public BsonCreatorMap MapConstructor(ConstructorInfo constructorInfo)
+ {
+ if (constructorInfo == null)
+ {
+ throw new ArgumentNullException("constructorInfo");
+ }
+ EnsureMemberInfoIsForThisClass(constructorInfo);
+
+ if (_frozen) { ThrowFrozenException(); }
+ var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == constructorInfo);
+ if (creatorMap == null)
+ {
+ var @delegate = new CreatorMapDelegateCompiler().CompileConstructorDelegate(constructorInfo);
+ creatorMap = new BsonCreatorMap(this, constructorInfo, @delegate);
+ _creatorMaps.Add(creatorMap);
+ }
+ return creatorMap;
+ }
+
+ /// <summary>
+ /// Creates a creator map for a constructor and adds it to the class map.
+ /// </summary>
+ /// <param name="constructorInfo">The constructor info.</param>
+ /// <param name="argumentNames">The argument names.</param>
+ /// <returns>The creator map (so method calls can be chained).</returns>
+ public BsonCreatorMap MapConstructor(ConstructorInfo constructorInfo, params string[] argumentNames)
+ {
+ var creatorMap = MapConstructor(constructorInfo);
+ creatorMap.SetArguments(argumentNames);
+ return creatorMap;
+ }
+
+ /// <summary>
+ /// Creates a creator map and adds it to the class.
+ /// </summary>
+ /// <param name="delegate">The delegate.</param>
+ /// <returns>The factory method map (so method calls can be chained).</returns>
+ public BsonCreatorMap MapCreator(Delegate @delegate)
+ {
+ if (@delegate == null)
+ {
+ throw new ArgumentNullException("delegate");
+ }
+
+ if (_frozen) { ThrowFrozenException(); }
+ var creatorMap = new BsonCreatorMap(this, null, @delegate);
+ _creatorMaps.Add(creatorMap);
+ return creatorMap;
+ }
+
+ /// <summary>
+ /// Creates a creator map and adds it to the class.
+ /// </summary>
+ /// <param name="delegate">The delegate.</param>
+ /// <param name="argumentNames">The argument names.</param>
+ /// <returns>The factory method map (so method calls can be chained).</returns>
+ public BsonCreatorMap MapCreator(Delegate @delegate, params string[] argumentNames)
+ {
+ var creatorMap = MapCreator(@delegate);
+ creatorMap.SetArguments(argumentNames);
+ return creatorMap;
+ }
+
+ /// <summary>
/// Creates a member map for the extra elements field and adds it to the class map.
/// </summary>
/// <param name="fieldName">The name of the extra elements field.</param>
@@ -676,6 +767,43 @@ public BsonMemberMap MapExtraElementsProperty(string propertyName)
}
/// <summary>
+ /// Creates a creator map for a factory method and adds it to the class.
+ /// </summary>
+ /// <param name="methodInfo">The method info.</param>
+ /// <returns>The creator map (so method calls can be chained).</returns>
+ public BsonCreatorMap MapFactoryMethod(MethodInfo methodInfo)
+ {
+ if (methodInfo == null)
+ {
+ throw new ArgumentNullException("methodInfo");
+ }
+ EnsureMemberInfoIsForThisClass(methodInfo);
+
+ if (_frozen) { ThrowFrozenException(); }
+ var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == methodInfo);
+ if (creatorMap == null)
+ {
+ var @delegate = new CreatorMapDelegateCompiler().CompileFactoryMethodDelegate(methodInfo);
+ creatorMap = new BsonCreatorMap(this, methodInfo, @delegate);
+ _creatorMaps.Add(creatorMap);
+ }
+ return creatorMap;
+ }
+
+ /// <summary>
+ /// Creates a creator map for a factory method and adds it to the class.
+ /// </summary>
+ /// <param name="methodInfo">The method info.</param>
+ /// <param name="argumentNames">The argument names.</param>
+ /// <returns>The creator map (so method calls can be chained).</returns>
+ public BsonCreatorMap MapFactoryMethod(MethodInfo methodInfo, params string[] argumentNames)
+ {
+ var creatorMap = MapFactoryMethod(methodInfo);
+ creatorMap.SetArguments(argumentNames);
+ return creatorMap;
+ }
+
+ /// <summary>
/// Creates a member map for a field and adds it to the class map.
/// </summary>
/// <param name="fieldName">The name of the field.</param>
@@ -762,6 +890,10 @@ public BsonMemberMap MapMember(MemberInfo memberInfo)
{
throw new ArgumentNullException("memberInfo");
}
+ if (!(memberInfo is FieldInfo) && !(memberInfo is PropertyInfo))
+ {
+ throw new ArgumentException("MemberInfo must be either a FieldInfo or a PropertyInfo.", "memberInfo");
+ }
EnsureMemberInfoIsForThisClass(memberInfo);
if (_frozen) { ThrowFrozenException(); }
@@ -803,6 +935,7 @@ public void Reset()
{
if (_frozen) { ThrowFrozenException(); }
+ _creatorMaps.Clear();
_creator = null;
_declaredMemberMaps.Clear();
_discriminator = _classType.Name;
@@ -936,6 +1069,46 @@ public void SetIsRootClass(bool isRootClass)
}
/// <summary>
+ /// Removes a creator map for a constructor from the class map.
+ /// </summary>
+ /// <param name="constructorInfo">The constructor info.</param>
+ public void UnmapConstructor(ConstructorInfo constructorInfo)
+ {
+ if (constructorInfo == null)
+ {
+ throw new ArgumentNullException("constructorInfo");
+ }
+ EnsureMemberInfoIsForThisClass(constructorInfo);
+
+ if (_frozen) { ThrowFrozenException(); }
+ var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == constructorInfo);
+ if (creatorMap != null)
+ {
+ _creatorMaps.Remove(creatorMap);
+ }
+ }
+
+ /// <summary>
+ /// Removes a creator map for a factory method from the class map.
+ /// </summary>
+ /// <param name="methodInfo">The method info.</param>
+ public void UnmapFactoryMethod(MethodInfo methodInfo)
+ {
+ if (methodInfo == null)
+ {
+ throw new ArgumentNullException("methodInfo");
+ }
+ EnsureMemberInfoIsForThisClass(methodInfo);
+
+ if (_frozen) { ThrowFrozenException(); }
+ var creatorMap = _creatorMaps.FirstOrDefault(m => m.MemberInfo == methodInfo);
+ if (creatorMap != null)
+ {
+ _creatorMaps.Remove(creatorMap);
+ }
+ }
+
+ /// <summary>
/// Removes the member map for a field from the class map.
/// </summary>
/// <param name="fieldName">The name of the field.</param>
@@ -1207,6 +1380,25 @@ public BsonMemberMap GetMemberMap<TMember>(Expression<Func<TClass, TMember>> mem
}
/// <summary>
+ /// Creates a creator map and adds it to the class map.
+ /// </summary>
+ /// <param name="creatorLambda">Lambda expression specifying the creator code and parameters to use.</param>
+ /// <returns>The member map.</returns>
+ public BsonCreatorMap MapCreator(Expression<Func<TClass, TClass>> creatorLambda)
+ {
+ if (creatorLambda == null)
+ {
+ throw new ArgumentNullException("creatorLambda");
+ }
+
+ IEnumerable<MemberInfo> arguments;
+ var @delegate = new CreatorMapDelegateCompiler().CompileCreatorDelegate(creatorLambda, out arguments);
+ var creatorMap = MapCreator(@delegate);
+ creatorMap.SetArguments(arguments);
+ return creatorMap;
+ }
+
+ /// <summary>
/// Creates a member map for the extra elements field and adds it to the class map.
/// </summary>
/// <typeparam name="TMember">The member type.</typeparam>
View
242 MongoDB.Bson/Serialization/BsonCreatorMap.cs
@@ -0,0 +1,242 @@
+/* Copyright 2010-2013 10gen 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
+{
+ /// <summary>
+ /// Represents a mapping to a delegate and its arguments.
+ /// </summary>
+ 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 IEnumerable<MemberInfo> _arguments; // the members that define the values for the delegate's parameters
+
+ // these values are set when Freeze is called
+ private IEnumerable<string> _elementNames; // the element names of the serialized arguments
+ private Dictionary<string, object> _defaultValues; // not all arguments have default values
+
+ // constructors
+ /// <summary>
+ /// Initializes a new instance of the BsonCreatorMap class.
+ /// </summary>
+ /// <param name="classMap">The class map.</param>
+ /// <param name="memberInfo">The member info (null if none).</param>
+ /// <param name="delegate">The delegate.</param>
+ 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
+ /// <summary>
+ /// Gets the arguments.
+ /// </summary>
+ public IEnumerable<MemberInfo> Arguments
+ {
+ get { return _arguments; }
+ }
+
+ /// <summary>
+ /// Gets the class map that this creator map belongs to.
+ /// </summary>
+ public BsonClassMap ClassMap
+ {
+ get { return _classMap; }
+ }
+
+ /// <summary>
+ /// Gets the delegeate
+ /// </summary>
+ public Delegate Delegate
+ {
+ get { return _delegate; }
+ }
+
+ /// <summary>
+ /// Gets the element names.
+ /// </summary>
+ public IEnumerable<string> ElementNames
+ {
+ get
+ {
+ if (!_isFrozen) { ThrowNotFrozenException(); }
+ return _elementNames;
+ }
+ }
+
+ /// <summary>
+ /// Gets the member info (null if none).
+ /// </summary>
+ public MemberInfo MemberInfo
+ {
+ get { return _memberInfo; }
+ }
+
+ // public methods
+ /// <summary>
+ /// Freezes the creator map.
+ /// </summary>
+ public void Freeze()
+ {
+ if (!_isFrozen)
+ {
+ var allMemberMaps = _classMap.AllMemberMaps;
+
+ var elementNames = new List<string>();
+ var defaultValues = new Dictionary<string, object>();
+ if (_arguments != null)
+ {
+ foreach (var argument in _arguments)
+ {
+ // compare MetadataTokens because ReflectedTypes could be different (see p. 774-5 of C# 5.0 In a Nutshell)
+ var memberMap = allMemberMaps.FirstOrDefault(m => m.MemberInfo.MetadataToken == argument.MetadataToken);
+ 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);
+ }
+ }
+ }
+
+ _elementNames = elementNames;
+ _defaultValues = defaultValues;
+ _isFrozen = true;
+ }
+ }
+
+ /// <summary>
+ /// Gets whether there is a default value for a missing element.
+ /// </summary>
+ /// <param name="elementName">The element name.</param>
+ /// <returns>True if there is a default value for element name; otherwise, false.</returns>
+ public bool HasDefaultValue(string elementName)
+ {
+ if (!_isFrozen) { ThrowNotFrozenException(); }
+ return _defaultValues.ContainsKey(elementName);
+ }
+
+ /// <summary>
+ /// Sets the arguments for the creator map.
+ /// </summary>
+ /// <param name="arguments">The arguments.</param>
+ /// <returns>The creator map.</returns>
+ public BsonCreatorMap SetArguments(IEnumerable<MemberInfo> arguments)
+ {
+ if (arguments == null)
+ {
+ throw new ArgumentNullException("arguments");
+ }
+ if (_isFrozen) { ThrowFrozenException(); }
+ _arguments = new List<MemberInfo>(arguments);
+ return this;
+ }
+
+ /// <summary>
+ /// Sets the arguments for the creator map.
+ /// </summary>
+ /// <param name="argumentNames">The argument names.</param>
+ /// <returns>The creator map.</returns>
+ public BsonCreatorMap SetArguments(IEnumerable<string> argumentNames)
+ {
+ if (argumentNames == null)
+ {
+ throw new ArgumentNullException("argumentNames");
+ }
+ if (_isFrozen) { ThrowFrozenException(); }
+
+ var arguments = new List<MemberInfo>();
+ foreach (var argumentName in argumentNames)
+ {
+ var memberTypes = MemberTypes.Field | MemberTypes.Property;
+ var bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
+ var memberInfos = _classMap.ClassType.GetMember(argumentName, memberTypes, bindingAttr);
+ 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<string, object> values)
+ {
+ var arguments = new List<object>();
+
+ // get the values for the arguments to be passed to the creator delegate
+ foreach (var elementName in _elementNames)
+ {
+ object argument;
+ if (values.TryGetValue(elementName, out argument))
+ {
+ values.Remove(elementName);
+ }
+ else if (!_defaultValues.TryGetValue(elementName, out argument))
+ {
+ // shouldn't happen unless there is a bug in ChooseBestCreator
+ throw new BsonInternalException();
+ }
+ arguments.Add(argument);
+ }
+
+ return _delegate.DynamicInvoke(arguments.ToArray());
+ }
+
+ // private methods
+ private void ThrowFrozenException()
+ {
+ throw new InvalidOperationException("BsonCreatorMap is frozen.");
+ }
+
+ private void ThrowNotFrozenException()
+ {
+ throw new InvalidOperationException("BsonCreatorMap is not frozen.");
+ }
+ }
+}
View
8 MongoDB.Bson/Serialization/BsonMemberMap.cs
@@ -175,6 +175,14 @@ public IIdGenerator IdGenerator
}
/// <summary>
+ /// Gets whether a default value was specified.
+ /// </summary>
+ public bool IsDefaultValueSpecified
+ {
+ get { return _defaultValueSpecified; }
+ }
+
+ /// <summary>
/// Gets whether an element is required for this member when deserialized.
/// </summary>
public bool IsRequired
View
37 MongoDB.Bson/Serialization/Conventions/AttributeConventionPack.cs
@@ -60,7 +60,7 @@ public IEnumerable<IConvention> Conventions
}
// nested classes
- private class AttributeConvention : ConventionBase, IClassMapConvention, IMemberMapConvention, IPostProcessingConvention
+ private class AttributeConvention : ConventionBase, IClassMapConvention, ICreatorMapConvention, IMemberMapConvention, IPostProcessingConvention
{
// public methods
public void Apply(BsonClassMap classMap)
@@ -81,10 +81,22 @@ public void Apply(BsonClassMap classMap)
#pragma warning restore 618
OptInMembersWithBsonMemberMapModifierAttribute(classMap);
+ OptInMembersWithBsonCreatorMapModifierAttribute(classMap);
IgnoreMembersWithBsonIgnoreAttribute(classMap);
ThrowForDuplicateMemberMapAttributes(classMap);
}
+ public void Apply(BsonCreatorMap creatorMap)
+ {
+ if (creatorMap.MemberInfo != null)
+ {
+ foreach (IBsonCreatorMapAttribute attribute in creatorMap.MemberInfo.GetCustomAttributes(typeof(IBsonCreatorMapAttribute), false))
+ {
+ attribute.Apply(creatorMap);
+ }
+ }
+ }
+
public void Apply(BsonMemberMap memberMap)
{
foreach (IBsonMemberMapAttribute attribute in memberMap.MemberInfo.GetCustomAttributes(typeof(IBsonMemberMapAttribute), false))
@@ -121,6 +133,29 @@ private bool AllowsDuplicate(Type type)
return usageAttribute == null || usageAttribute.AllowMultipleMembers;
}
+ private void OptInMembersWithBsonCreatorMapModifierAttribute(BsonClassMap classMap)
+ {
+ // let other constructors opt-in if they have any IBsonCreatorMapAttribute attributes
+ foreach (var constructorInfo in classMap.ClassType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
+ {
+ var hasAttribute = constructorInfo.GetCustomAttributes(typeof(IBsonCreatorMapAttribute), false).Any();
+ if (hasAttribute)
+ {
+ classMap.MapConstructor(constructorInfo);
+ }
+ }
+
+ // let other static factory methods opt-in if they have any IBsonCreatorMapAttribute attributes
+ foreach (var methodInfo in classMap.ClassType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
+ {
+ var hasAttribute = methodInfo.GetCustomAttributes(typeof(IBsonCreatorMapAttribute), false).Any();
+ if (hasAttribute)
+ {
+ classMap.MapFactoryMethod(methodInfo);
+ }
+ }
+ }
+
private void OptInMembersWithBsonMemberMapModifierAttribute(BsonClassMap classMap)
{
// let other fields opt-in if they have any IBsonMemberMapAttribute attributes
View
8 MongoDB.Bson/Serialization/Conventions/ConventionRunner.cs
@@ -62,6 +62,14 @@ public void Apply(BsonClassMap classMap)
}
}
+ foreach (var convention in _conventions.OfType<ICreatorMapConvention>())
+ {
+ foreach (var creatorMap in classMap.CreatorMaps)
+ {
+ convention.Apply(creatorMap);
+ }
+ }
+
foreach (var convention in _conventions.OfType<IPostProcessingConvention>())
{
convention.PostProcess(classMap);
View
1 MongoDB.Bson/Serialization/Conventions/DefaultConventionPack.cs
@@ -40,6 +40,7 @@ private DefaultConventionPack()
new NamedIdMemberConvention(new [] { "Id", "id", "_id" }),
new NamedExtraElementsMemberConvention(new [] { "ExtraElements" }),
new IgnoreExtraElementsConvention(false),
+ new NamedParameterCreatorMapConvention(),
new StringObjectIdIdGeneratorConvention(), // should be before LookupIdGeneratorConvention
new LookupIdGeneratorConvention()
};
View
29 MongoDB.Bson/Serialization/Conventions/ICreatorMapConvention.cs
@@ -0,0 +1,29 @@
+/* Copyright 2010-2013 10gen 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.
+*/
+
+namespace MongoDB.Bson.Serialization.Conventions
+{
+ /// <summary>
+ /// Represents a convention that applies to a BsonCreatorMap.
+ /// </summary>
+ public interface ICreatorMapConvention : IConvention
+ {
+ /// <summary>
+ /// Applies a modification to the creator map.
+ /// </summary>
+ /// <param name="creatorMap">The creator map.</param>
+ void Apply(BsonCreatorMap creatorMap);
+ }
+}
View
128 MongoDB.Bson/Serialization/Conventions/NamedParameterCreatorMapConvention.cs
@@ -0,0 +1,128 @@
+/* Copyright 2010-2013 10gen 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.Reflection;
+
+namespace MongoDB.Bson.Serialization.Conventions
+{
+ /// <summary>
+ /// A convention that uses the names of the creator parameters to find the matching members.
+ /// </summary>
+ public class NamedParameterCreatorMapConvention : ConventionBase, ICreatorMapConvention
+ {
+ // public methods
+ /// <summary>
+ /// Applies a modification to the creator map.
+ /// </summary>
+ /// <param name="creatorMap">The creator map.</param>
+ public void Apply(BsonCreatorMap creatorMap)
+ {
+ if (creatorMap.Arguments == null)
+ {
+ if (creatorMap.MemberInfo != null)
+ {
+ var parameters = GetParameters(creatorMap.MemberInfo);
+ if (parameters != null)
+ {
+ var arguments = new List<MemberInfo>();
+
+ foreach (var parameter in parameters)
+ {
+ var argument = FindMatchingArgument(creatorMap.ClassMap.ClassType, parameter);
+ if (argument == null)
+ {
+ var message = string.Format("Unable to find a matching member to provide the value for parameter '{0}'.", parameter.Name);
+ }
+ arguments.Add(argument);
+ }
+
+ creatorMap.SetArguments(arguments);
+ }
+ }
+ }
+ }
+
+ // private methods
+ private MemberInfo FindMatchingArgument(Type classType, ParameterInfo parameter)
+ {
+ MemberInfo argument;
+ if ((argument = Match(classType, MemberTypes.Property, BindingFlags.Public, parameter)) != null)
+ {
+ return argument;
+ }
+ if ((argument = Match(classType, MemberTypes.Field, BindingFlags.Public, parameter)) != null)
+ {
+ return argument;
+ }
+ if ((argument = Match(classType, MemberTypes.Property, BindingFlags.NonPublic, parameter)) != null)
+ {
+ return argument;
+ }
+ if ((argument = Match(classType, MemberTypes.Field, BindingFlags.NonPublic, parameter)) != null)
+ {
+ return argument;
+ }
+ return null;
+ }
+
+ private Type GetMemberType(MemberInfo memberInfo)
+ {
+ var fieldInfo = memberInfo as FieldInfo;
+ if (fieldInfo != null)
+ {
+ return fieldInfo.FieldType;
+ }
+
+ var propertyInfo = memberInfo as PropertyInfo;
+ if (propertyInfo != null)
+ {
+ return propertyInfo.PropertyType;
+ }
+
+ // should never happen
+ throw new BsonInternalException();
+ }
+
+ private IEnumerable<ParameterInfo> GetParameters(MemberInfo memberInfo)
+ {
+ var constructorInfo = memberInfo as ConstructorInfo;
+ if (constructorInfo != null)
+ {
+ return constructorInfo.GetParameters();
+ }
+
+ var methodInfo = memberInfo as MethodInfo;
+ if (methodInfo != null)
+ {
+ return methodInfo.GetParameters();
+ }
+
+ return null;
+ }
+
+ private MemberInfo Match(Type classType, MemberTypes memberType, BindingFlags visibility, ParameterInfo parameter)
+ {
+ var bindingAttr = BindingFlags.IgnoreCase | BindingFlags.Instance;
+ var memberInfos = classType.GetMember(parameter.Name, memberType, bindingAttr | visibility);
+ if (memberInfos.Length == 1 && GetMemberType(memberInfos[0]) == parameter.ParameterType)
+ {
+ return memberInfos[0];
+ }
+ return null;
+ }
+ }
+}
View
131 MongoDB.Bson/Serialization/CreatorMapDelegateCompiler.cs
@@ -0,0 +1,131 @@
+/* Copyright 2010-2013 10gen 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 System.Linq.Expressions;
+using System.Reflection;
+using System.Collections.Generic;
+
+namespace MongoDB.Bson.Serialization
+{
+ /// <summary>
+ /// A helper class used to create and compile delegates for creator maps.
+ /// </summary>
+ public class CreatorMapDelegateCompiler : ExpressionVisitor
+ {
+ // private fields
+ private Type _class;
+ private ParameterExpression _prototypeParameter;
+ private Dictionary<MemberInfo, ParameterExpression> _parameters;
+
+ // public methods
+ /// <summary>
+ /// Creates and compiles a delegate that calls a constructor.
+ /// </summary>
+ /// <param name="constructorInfo">The constructor.</param>
+ /// <returns>A delegate that calls the constructor.</returns>
+ public Delegate CompileConstructorDelegate(ConstructorInfo constructorInfo)
+ {
+ // build and compile the following delegate:
+ // (p1, p2, ...) => new TClass(p1, p2, ...)
+
+ var parameters = constructorInfo.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
+ var body = Expression.New(constructorInfo, parameters);
+ var lambda = Expression.Lambda(body, parameters);
+ return lambda.Compile();
+ }
+
+ /// <summary>
+ /// Creates and compiles a delegate from a lambda expression.
+ /// </summary>
+ /// <typeparam name="TClass">The type of the class.</typeparam>
+ /// <param name="creatorLambda">The lambda expression.</param>
+ /// <param name="arguments">The arguments for the delegate's parameters.</param>
+ /// <returns>A delegate.</returns>
+ public Delegate CompileCreatorDelegate<TClass>(Expression<Func<TClass, TClass>> creatorLambda, out IEnumerable<MemberInfo> arguments)
+ {
+ // transform c => expression (where c is the prototype parameter)
+ // to (p1, p2, ...) => expression' where expression' is expression with every c.X replaced by p#
+
+ _class = typeof(TClass); // not creatorLambda.Type in case lambda returns a subtype of TClass
+ _prototypeParameter = creatorLambda.Parameters[0];
+ _parameters = new Dictionary<MemberInfo, ParameterExpression>();
+ var body = Visit(creatorLambda.Body);
+ var lambda = Expression.Lambda(body, _parameters.Values.ToArray());
+ var @delegate = lambda.Compile();
+
+ arguments = _parameters.Keys.ToArray();
+ return @delegate;
+ }
+
+ /// <summary>
+ /// Creates and compiles a delegate that calls a factory method.
+ /// </summary>
+ /// <param name="methodInfo">the method.</param>
+ /// <returns>A delegate that calls the factory method.</returns>
+ public Delegate CompileFactoryMethodDelegate(MethodInfo methodInfo)
+ {
+ // build and compile the following delegate:
+ // (p1, p2, ...) => factoryMethod(p1, p2, ...)
+
+ var parameters = methodInfo.GetParameters().Select(p => Expression.Parameter(p.ParameterType, p.Name)).ToArray();
+ var body = Expression.Call(methodInfo, parameters);
+ var lambda = Expression.Lambda(body, parameters);
+ return lambda.Compile();
+ }
+
+ // protected methods
+ /// <summary>
+ /// Visits a MemberExpression.
+ /// </summary>
+ /// <param name="node">The MemberExpression.</param>
+ /// <returns>The MemberExpression (possibly modified).</returns>
+ protected override Expression VisitMember(MemberExpression node)
+ {
+ if (node.Expression == _prototypeParameter)
+ {
+ var memberInfo = node.Member;
+
+ ParameterExpression parameter;
+ if (!_parameters.TryGetValue(memberInfo, out parameter))
+ {
+ var parameterName = string.Format("_p{0}_", _parameters.Count + 1); // avoid naming conflicts with body
+ parameter = Expression.Parameter(node.Type, parameterName);
+ _parameters.Add(memberInfo, parameter);
+ }
+
+ return parameter;
+ }
+
+ return base.VisitMember(node);
+ }
+
+ /// <summary>
+ /// Visits a ParameterExpression.
+ /// </summary>
+ /// <param name="node">The ParameterExpression.</param>
+ /// <returns>The ParameterExpression (possibly modified).</returns>
+ protected override Expression VisitParameter(ParameterExpression node)
+ {
+ if (node == _prototypeParameter)
+ {
+ throw new BsonSerializationException("The only operations allowed on the prototype parameter are accessing a field or property.");
+ }
+
+ return base.VisitParameter(node);
+ }
+ }
+}
View
531 MongoDB.Bson/Serialization/ExpressionVisitor.cs
@@ -0,0 +1,531 @@
+/* Copyright 2010-2013 10gen 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.Collections.ObjectModel;
+using System.Linq.Expressions;
+
+namespace MongoDB.Bson.Serialization
+{
+ // TODO: this class duplicates a similar class in MongoDB.Driver.dll
+ // when we move to .NET Framework 4 we can use .NET's version of ExpressionVisitor and eliminate the duplication
+
+ /// <summary>
+ /// An abstract base class for an Expression visitor.
+ /// </summary>
+ public abstract class ExpressionVisitor
+ {
+ // constructors
+ /// <summary>
+ /// Initializes a new instance of the ExpressionVisitor class.
+ /// </summary>
+ protected ExpressionVisitor()
+ {
+ }
+
+ // protected methods
+ /// <summary>
+ /// Visits an Expression.
+ /// </summary>
+ /// <param name="node">The Expression.</param>
+ /// <returns>The Expression (posibly modified).</returns>
+ protected virtual Expression Visit(Expression node)
+ {
+ if (node == null)
+ {
+ return node;
+ }
+ switch (node.NodeType)
+ {
+ case ExpressionType.Negate:
+ case ExpressionType.NegateChecked:
+ case ExpressionType.Not:
+ case ExpressionType.Convert:
+ case ExpressionType.ConvertChecked:
+ case ExpressionType.ArrayLength:
+ case ExpressionType.Quote:
+ case ExpressionType.TypeAs:
+ return this.VisitUnary((UnaryExpression)node);
+ case ExpressionType.Add:
+ case ExpressionType.AddChecked:
+ case ExpressionType.Subtract:
+ case ExpressionType.SubtractChecked:
+ case ExpressionType.Multiply:
+ case ExpressionType.MultiplyChecked:
+ case ExpressionType.Divide:
+ case ExpressionType.Modulo:
+ case ExpressionType.And:
+ case ExpressionType.AndAlso:
+ case ExpressionType.Or:
+ case ExpressionType.OrElse:
+ case ExpressionType.LessThan:
+ case ExpressionType.LessThanOrEqual:
+ case ExpressionType.GreaterThan:
+ case ExpressionType.GreaterThanOrEqual:
+ case ExpressionType.Equal:
+ case ExpressionType.NotEqual:
+ case ExpressionType.Coalesce:
+ case ExpressionType.ArrayIndex:
+ case ExpressionType.RightShift:
+ case ExpressionType.LeftShift:
+ case ExpressionType.ExclusiveOr:
+ return this.VisitBinary((BinaryExpression)node);
+ case ExpressionType.TypeIs:
+ return this.VisitTypeBinary((TypeBinaryExpression)node);
+ case ExpressionType.Conditional:
+ return this.VisitConditional((ConditionalExpression)node);
+ case ExpressionType.Constant:
+ return this.VisitConstant((ConstantExpression)node);
+ case ExpressionType.Parameter:
+ return this.VisitParameter((ParameterExpression)node);
+ case ExpressionType.MemberAccess:
+ return this.VisitMember((MemberExpression)node);
+ case ExpressionType.Call:
+ return this.VisitMethodCall((MethodCallExpression)node);
+ case ExpressionType.Lambda:
+ return this.VisitLambda((LambdaExpression)node);
+ case ExpressionType.New:
+ return this.VisitNew((NewExpression)node);
+ case ExpressionType.NewArrayInit:
+ case ExpressionType.NewArrayBounds:
+ return this.VisitNewArray((NewArrayExpression)node);
+ case ExpressionType.Invoke:
+ return this.VisitInvocation((InvocationExpression)node);
+ case ExpressionType.MemberInit:
+ return this.VisitMemberInit((MemberInitExpression)node);
+ case ExpressionType.ListInit:
+ return this.VisitListInit((ListInitExpression)node);
+ default:
+ throw new Exception(string.Format("Unhandled expression type: '{0}'", node.NodeType));
+ }
+ }
+
+ /// <summary>
+ /// Visits an Expression list.
+ /// </summary>
+ /// <param name="nodes">The Expression list.</param>
+ /// <returns>The Expression list (possibly modified).</returns>
+ protected ReadOnlyCollection<Expression> Visit(ReadOnlyCollection<Expression> nodes)
+ {
+ List<Expression> list = null;
+ for (int i = 0, n = nodes.Count; i < n; i++)
+ {
+ Expression node = this.Visit(nodes[i]);
+ if (list != null)
+ {
+ list.Add(node);
+ }
+ else if (node != nodes[i])
+ {
+ list = new List<Expression>(n);
+ for (int j = 0; j < i; j++)
+ {
+ list.Add(nodes[j]);
+ }
+ list.Add(node);
+ }
+ }
+ if (list != null)
+ {
+ return list.AsReadOnly();
+ }
+ return nodes;
+ }
+
+ /// <summary>
+ /// Visits a BinaryExpression.
+ /// </summary>
+ /// <param name="node">The BinaryExpression.</param>
+ /// <returns>The BinaryExpression (possibly modified).</returns>
+ protected virtual Expression VisitBinary(BinaryExpression node)
+ {
+ Expression left = this.Visit(node.Left);
+ Expression right = this.Visit(node.Right);
+ Expression conversion = this.Visit(node.Conversion);
+ if (left != node.Left || right != node.Right || conversion != node.Conversion)
+ {
+ if (node.NodeType == ExpressionType.Coalesce && node.Conversion != null)
+ {
+ return Expression.Coalesce(left, right, conversion as LambdaExpression);
+ }
+ else
+ {
+ return Expression.MakeBinary(node.NodeType, left, right, node.IsLiftedToNull, node.Method);
+ }
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a ConditionalExpression.
+ /// </summary>
+ /// <param name="node">The ConditionalExpression.</param>
+ /// <returns>The ConditionalExpression (possibly modified).</returns>
+ protected virtual Expression VisitConditional(ConditionalExpression node)
+ {
+ Expression test = this.Visit(node.Test);
+ Expression ifTrue = this.Visit(node.IfTrue);
+ Expression ifFalse = this.Visit(node.IfFalse);
+ if (test != node.Test || ifTrue != node.IfTrue || ifFalse != node.IfFalse)
+ {
+ return Expression.Condition(test, ifTrue, ifFalse);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a ConstantExpression.
+ /// </summary>
+ /// <param name="node">The ConstantExpression.</param>
+ /// <returns>The ConstantExpression (possibly modified).</returns>
+ protected virtual Expression VisitConstant(ConstantExpression node)
+ {
+ return node;
+ }
+
+ /// <summary>
+ /// Visits an ElementInit.
+ /// </summary>
+ /// <param name="node">The ElementInit.</param>
+ /// <returns>The ElementInit (possibly modified).</returns>
+ protected virtual ElementInit VisitElementInit(ElementInit node)
+ {
+ ReadOnlyCollection<Expression> arguments = this.Visit(node.Arguments);
+ if (arguments != node.Arguments)
+ {
+ return Expression.ElementInit(node.AddMethod, arguments);
+ }
+ return node;
+ }
+
+ // TODO: the .NET Framework 4 version of ExpressionVisitor does not have a method called VisitElementInitializerList
+ // leaving this method for now, though perhaps it could be replaced with Visit(ReadOnlyCollection<Expression>)?
+
+ /// <summary>
+ /// Visits an ElementInit list.
+ /// </summary>
+ /// <param name="nodes">The ElementInit list.</param>
+ /// <returns>The ElementInit list (possibly modified).</returns>
+ protected virtual IEnumerable<ElementInit> VisitElementInitList(
+ ReadOnlyCollection<ElementInit> nodes)
+ {
+ List<ElementInit> list = null;
+ for (int i = 0, n = nodes.Count; i < n; i++)
+ {
+ ElementInit node = this.VisitElementInit(nodes[i]);
+ if (list != null)
+ {
+ list.Add(node);
+ }
+ else if (node != nodes[i])
+ {
+ list = new List<ElementInit>(n);
+ for (int j = 0; j < i; j++)
+ {
+ list.Add(nodes[j]);
+ }
+ list.Add(node);
+ }
+ }
+ if (list != null)
+ {
+ return list;
+ }
+ return nodes;
+ }
+
+ /// <summary>
+ /// Visits an InvocationExpression.
+ /// </summary>
+ /// <param name="node">The InvocationExpression.</param>
+ /// <returns>The InvocationExpression (possibly modified).</returns>
+ protected virtual Expression VisitInvocation(InvocationExpression node)
+ {
+ IEnumerable<Expression> args = this.Visit(node.Arguments);
+ Expression expr = this.Visit(node.Expression);
+ if (args != node.Arguments || expr != node.Expression)
+ {
+ return Expression.Invoke(expr, args);
+ }
+ return node;
+ }
+
+ // TODO: in .NET Framework 4 VisitLambda takes an Expression<T> instead of Lambda
+ // probably not worthing changing in our version of ExpressionVisitor
+
+ /// <summary>
+ /// Visits a LambdaExpression.
+ /// </summary>
+ /// <param name="node">The LambdaExpression.</param>
+ /// <returns>The LambdaExpression (possibly modified).</returns>
+ protected virtual Expression VisitLambda(LambdaExpression node)
+ {
+ Expression body = this.Visit(node.Body);
+ if (body != node.Body)
+ {
+ return Expression.Lambda(node.Type, body, node.Parameters);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a ListInitExpression.
+ /// </summary>
+ /// <param name="node">The ListInitExpression.</param>
+ /// <returns>The ListInitExpression (possibly modified).</returns>
+ protected virtual Expression VisitListInit(ListInitExpression node)
+ {
+ NewExpression n = this.VisitNew(node.NewExpression);
+ IEnumerable<ElementInit> initializers = this.VisitElementInitList(node.Initializers);
+ if (n != node.NewExpression || initializers != node.Initializers)
+ {
+ return Expression.ListInit(n, initializers);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a MemberExpression.
+ /// </summary>
+ /// <param name="node">The MemberExpression.</param>
+ /// <returns>The MemberExpression (possibly modified).</returns>
+ protected virtual Expression VisitMember(MemberExpression node)
+ {
+ Expression exp = this.Visit(node.Expression);
+ if (exp != node.Expression)
+ {
+ return Expression.MakeMemberAccess(exp, node.Member);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a MemberAssignment.
+ /// </summary>
+ /// <param name="node">The MemberAssignment.</param>
+ /// <returns>The MemberAssignment (possibly modified).</returns>
+ protected virtual MemberAssignment VisitMemberAssignment(MemberAssignment node)
+ {
+ Expression e = this.Visit(node.Expression);
+ if (e != node.Expression)
+ {
+ return Expression.Bind(node.Member, e);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a MemberBinding.
+ /// </summary>
+ /// <param name="node">The MemberBinding.</param>
+ /// <returns>The MemberBinding (possibly modified).</returns>
+ protected virtual MemberBinding VisitMemberBinding(MemberBinding node)
+ {
+ switch (node.BindingType)
+ {
+ case MemberBindingType.Assignment:
+ return this.VisitMemberAssignment((MemberAssignment)node);
+ case MemberBindingType.MemberBinding:
+ return this.VisitMemberMemberBinding((MemberMemberBinding)node);
+ case MemberBindingType.ListBinding:
+ return this.VisitMemberListBinding((MemberListBinding)node);
+ default:
+ throw new Exception(string.Format("Unhandled binding type '{0}'", node.BindingType));
+ }
+ }
+
+ // TODO: the .NET Framework 4 version of ExpressionVisitor does not have a method called VisitMemberBindingList
+ // leaving this method for now, though perhaps it could be replaced with Visit(ReadOnlyCollection<Expression>)?
+
+ /// <summary>
+ /// Visits a MemberBinding list.
+ /// </summary>
+ /// <param name="nodes">The MemberBinding list.</param>
+ /// <returns>The MemberBinding list (possibly modified).</returns>
+ protected virtual IEnumerable<MemberBinding> VisitMemberBindingList(ReadOnlyCollection<MemberBinding> nodes)
+ {
+ List<MemberBinding> list = null;
+ for (int i = 0, n = nodes.Count; i < n; i++)
+ {
+ MemberBinding node = this.VisitMemberBinding(nodes[i]);
+ if (list != null)
+ {
+ list.Add(node);
+ }
+ else if (node != nodes[i])
+ {
+ list = new List<MemberBinding>(n);
+ for (int j = 0; j < i; j++)
+ {
+ list.Add(nodes[j]);
+ }
+ list.Add(node);
+ }
+ }
+ if (list != null)
+ {
+ return list;
+ }
+ return nodes;
+ }
+
+ /// <summary>
+ /// Visits a MemberInitExpression.
+ /// </summary>
+ /// <param name="node">The MemberInitExpression.</param>
+ /// <returns>The MemberInitExpression (possibly modified).</returns>
+ protected virtual Expression VisitMemberInit(MemberInitExpression node)
+ {
+ NewExpression n = this.VisitNew(node.NewExpression);
+ IEnumerable<MemberBinding> bindings = this.VisitMemberBindingList(node.Bindings);
+ if (n != node.NewExpression || bindings != node.Bindings)
+ {
+ return Expression.MemberInit(n, bindings);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a MemberListBinding.
+ /// </summary>
+ /// <param name="node">The MemberListBinding.</param>
+ /// <returns>The MemberListBinding (possibly modified).</returns>
+ protected virtual MemberListBinding VisitMemberListBinding(MemberListBinding node)
+ {
+ IEnumerable<ElementInit> initializers = this.VisitElementInitList(node.Initializers);
+ if (initializers != node.Initializers)
+ {
+ return Expression.ListBind(node.Member, initializers);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a MemberMemberBinding.
+ /// </summary>
+ /// <param name="node">The MemberMemberBinding.</param>
+ /// <returns>The MemberMemberBinding (possibly modified).</returns>
+ protected virtual MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
+ {
+ IEnumerable<MemberBinding> bindings = this.VisitMemberBindingList(node.Bindings);
+ if (bindings != node.Bindings)
+ {
+ return Expression.MemberBind(node.Member, bindings);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a MethodCallExpression.
+ /// </summary>
+ /// <param name="node">The MethodCallExpression.</param>
+ /// <returns>The MethodCallExpression (possibly modified).</returns>
+ protected virtual Expression VisitMethodCall(MethodCallExpression node)
+ {
+ Expression obj = this.Visit(node.Object);
+ IEnumerable<Expression> args = this.Visit(node.Arguments);
+ if (obj != node.Object || args != node.Arguments)
+ {
+ return Expression.Call(obj, node.Method, args);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a NewExpression.
+ /// </summary>
+ /// <param name="node">The NewExpression.</param>
+ /// <returns>The NewExpression (possibly modified).</returns>
+ protected virtual NewExpression VisitNew(NewExpression node)
+ {
+ IEnumerable<Expression> args = this.Visit(node.Arguments);
+ if (args != node.Arguments)
+ {
+ if (node.Members != null)
+ {
+ return Expression.New(node.Constructor, args, node.Members);
+ }
+ else
+ {
+ return Expression.New(node.Constructor, args);
+ }
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a NewArrayExpression.
+ /// </summary>
+ /// <param name="node">The NewArrayExpression.</param>
+ /// <returns>The NewArrayExpression (possibly modified).</returns>
+ protected virtual Expression VisitNewArray(NewArrayExpression node)
+ {
+ IEnumerable<Expression> exprs = this.Visit(node.Expressions);
+ if (exprs != node.Expressions)
+ {
+ if (node.NodeType == ExpressionType.NewArrayInit)
+ {
+ return Expression.NewArrayInit(node.Type.GetElementType(), exprs);
+ }
+ else
+ {
+ return Expression.NewArrayBounds(node.Type.GetElementType(), exprs);
+ }
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a ParameterExpression.
+ /// </summary>
+ /// <param name="node">The ParameterExpression.</param>
+ /// <returns>The ParameterExpression (possibly modified).</returns>
+ protected virtual Expression VisitParameter(ParameterExpression node)
+ {
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a TypeBinaryExpression.
+ /// </summary>
+ /// <param name="node">The TypeBinaryExpression.</param>
+ /// <returns>The TypeBinaryExpression (possibly modified).</returns>
+ protected virtual Expression VisitTypeBinary(TypeBinaryExpression node)
+ {
+ Expression expr = this.Visit(node.Expression);
+ if (expr != node.Expression)
+ {
+ return Expression.TypeIs(expr, node.TypeOperand);
+ }
+ return node;
+ }
+
+ /// <summary>
+ /// Visits a UnaryExpression.
+ /// </summary>
+ /// <param name="node">The UnaryExpression.</param>
+ /// <returns>The UnaryExpression (possibly modified).</returns>
+ protected virtual Expression VisitUnary(UnaryExpression node)
+ {
+ Expression operand = this.Visit(node.Operand);
+ if (operand != node.Operand)
+ {
+ return Expression.MakeUnary(node.NodeType, operand, node.Type, node.Method);
+ }
+ return node;
+ }
+ }
+}
View
24 MongoDB.Bson/Serialization/ICreatorSelector.cs
@@ -0,0 +1,24 @@
+/* Copyright 2010-2013 10gen 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.Collections.Generic;
+
+namespace MongoDB.Bson.Serialization
+{
+ internal interface ICreatorSelector
+ {
+ BsonCreatorMap SelectCreator(BsonClassMap classMap, Dictionary<string, object> values);
+ }
+}
View
100 MongoDB.Bson/Serialization/MostArgumentsCreatorSelector.cs
@@ -0,0 +1,100 @@
+/* Copyright 2010-2013 10gen 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.Collections.Generic;
+
+namespace MongoDB.Bson.Serialization
+{
+ internal class MostArgumentsCreatorSelector : ICreatorSelector
+ {
+ // public methods
+ public BsonCreatorMap SelectCreator(BsonClassMap classMap, Dictionary<string, object> values)
+ {
+ MatchData bestMatch = null;
+
+ foreach (var creatorMap in classMap.CreatorMaps)
+ {
+ var match = Match(creatorMap, values);
+ if (match != null)
+ {
+ if (bestMatch == null || IsBetterMatch(match, bestMatch))
+ {
+ bestMatch = match;
+ }
+ }
+ }
+
+ return (bestMatch == null) ? null : bestMatch.CreatorMap;
+ }
+
+ // private methods
+ private bool IsBetterMatch(MatchData lhs, MatchData rhs)
@tkellogg
tkellogg added a note Mar 9, 2013

Wow, this is pretty advanced. I wonder if it's possible to offload this work to the compiler via dynamic dispatch (i.e. cast things to dynamic and let the DLR do the overload resolution)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ if (lhs.ArgumentCount < rhs.ArgumentCount)
+ {
+ return false;
+ }
+ else if (lhs.ArgumentCount > rhs.ArgumentCount)
+ {
+ return true;
+ }
+ else if (lhs.DefaultValueCount < rhs.DefaultValueCount)
+ {
+ return false;
+ }
+ else if (lhs.DefaultValueCount > rhs.DefaultValueCount)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ private MatchData Match(BsonCreatorMap creatorMap, Dictionary<string, object> values)
+ {
+ var argumentCount = 0;
+ var defaultValueCount = 0;
+
+ // a creator is a match if we have a value for each parameter (either a deserialized value or a default value)
+ foreach (var elementName in creatorMap.ElementNames)
+ {
+ if (values.ContainsKey(elementName))
+ {
+ argumentCount++;
+ }
+ else if (creatorMap.HasDefaultValue(elementName))
+ {
+ defaultValueCount++;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ return new MatchData { CreatorMap = creatorMap, ArgumentCount = argumentCount, DefaultValueCount = defaultValueCount };
+ }
+
+ // nested classes
+ private class MatchData
+ {
+ public BsonCreatorMap CreatorMap;
+ public int ArgumentCount;
+ public int DefaultValueCount;
+ }
+ }
+}
View
126 MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
+using System.Linq;
using System.Reflection;
using MongoDB.Bson.IO;
using MongoDB.Bson.Serialization.Options;
@@ -115,7 +116,6 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
{
throw new InvalidOperationException("An anonymous class cannot be deserialized.");
}
- var obj = _classMap.CreateInstance();
if (bsonType != BsonType.Document)
{
@@ -125,10 +125,24 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
throw new FileFormatException(message);
}
- var supportsInitialization = obj as ISupportInitialize;
- if (supportsInitialization != null)
+ Dictionary<string, object> values = null;
+ object obj = null;
+ ISupportInitialize supportsInitialization = null;
+ if (_classMap.HasCreatorMaps)
{
- supportsInitialization.BeginInit();
+ // for creator-based deserialization we first gather the values in a dictionary and then call a matching creator
+ values = new Dictionary<string, object>();
+ }
+ else
+ {
+ // for mutable classes we deserialize the values directly into the result object
+ obj = _classMap.CreateInstance();
+
+ supportsInitialization = obj as ISupportInitialize;
+ if (supportsInitialization != null)
+ {
+ supportsInitialization.BeginInit();
+ }
}
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
@@ -148,13 +162,22 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
var memberMap = allMemberMaps[memberMapIndex];
if (memberMapIndex != extraElementsMemberMapIndex)
{
- if (memberMap.IsReadOnly)
+ if (obj != null)
{
- bsonReader.SkipValue();
+ if (memberMap.IsReadOnly)
+ {
+ bsonReader.SkipValue();
+ }
+ else
+ {
+ var value = DeserializeMemberValue(bsonReader, memberMap);
+ memberMap.Setter(obj, value);
+ }
}
else
{
- DeserializeMember(bsonReader, obj, memberMap);
+ var value = DeserializeMemberValue(bsonReader, memberMap);
+ values[elementName] = value;
}
}
else
@@ -198,7 +221,7 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
var memberMapBlock = ~memberMapBitArray[bitArrayIndex]; // notice that bits are flipped so 1's are now the missing elements
// work through this memberMapBlock of 32 elements
- for (;;)
+ while (true)
{
// examine missing elements (memberMapBlock is shifted right as we work through the block)
for (; (memberMapBlock & 1) != 0; ++memberMapIndex, memberMapBlock >>= 1)
@@ -217,7 +240,15 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
memberMap.ElementName, fieldOrProperty, memberMap.MemberName, _classMap.ClassType.FullName);
throw new FileFormatException(message);
}
- memberMap.ApplyDefaultValue(obj);
+
+ if (obj != null)
+ {
+ memberMap.ApplyDefaultValue(obj);
+ }
+ else if (memberMap.IsDefaultValueSpecified && !memberMap.IsReadOnly)
+ {
+ values[memberMap.ElementName] = memberMap.DefaultValue;
+ }
}
if (memberMapBlock == 0)
@@ -232,12 +263,20 @@ public object Deserialize(BsonReader bsonReader, Type nominalType, IBsonSerializ
}
}
- if (supportsInitialization != null)
+ if (obj != null)
{
- supportsInitialization.EndInit();
+ if (supportsInitialization != null)
+ {
+ supportsInitialization.EndInit();
+ }
+
+ return obj;
+ }
+ else
+ {
+ return CreateInstanceUsingCreator(values);
}
- return obj;
}
}
@@ -429,6 +468,52 @@ public void SetDocumentId(object document, object id)
}
// private methods
+ private BsonCreatorMap ChooseBestCreator(Dictionary<string, object> values)
+ {
+ // there's only one selector for now, but there might be more in the future (possibly even user provided)
+ var selector = new MostArgumentsCreatorSelector();
+ var creatorMap = selector.SelectCreator(_classMap, values);
+
+ if (creatorMap == null)
+ {
+ throw new BsonSerializationException("No matching creator found.");
+ }
+
+ return creatorMap;
+ }
+
+ private object CreateInstanceUsingCreator(Dictionary<string, object> values)
+ {
+ var creatorMap = ChooseBestCreator(values);
+ var obj = creatorMap.CreateInstance(values); // removes values consumed
+
+ var supportsInitialization = obj as ISupportInitialize;
+ if (supportsInitialization != null)
+ {
+ supportsInitialization.BeginInit();
+ }
+
+ // process any left over values that weren't passed to the creator
+ foreach (var keyValuePair in values)
+ {
+ var elementName = keyValuePair.Key;
+ var value = keyValuePair.Value;
+
+ var memberMap = _classMap.GetMemberMapForElement(elementName);
+ if (!memberMap.IsReadOnly)
+ {
+ memberMap.Setter.Invoke(obj, value);
+ }
+ }
+
+ if (supportsInitialization != null)
+ {
+ supportsInitialization.EndInit();
+ }
+
+ return obj;
+ }
+
private void DeserializeExtraElement(
BsonReader bsonReader,
object obj,
@@ -466,32 +551,31 @@ public void SetDocumentId(object document, object id)
}
}
- private void DeserializeMember(BsonReader bsonReader, object obj, BsonMemberMap memberMap)
+ private object DeserializeMemberValue(BsonReader bsonReader, BsonMemberMap memberMap)
{
try
{
var nominalType = memberMap.MemberType;
- object value = null;
var bsonType = bsonReader.GetCurrentBsonType();
if (bsonType == BsonType.Null && nominalType.IsInterface)
{
bsonReader.ReadNull();
- goto setvalue;
+ return null;
}
else if (memberMap.MemberTypeIsBsonValue)
{
if (bsonType == BsonType.Document && IsCSharpNullRepresentation(bsonReader))
{
// if IsCSharpNullRepresentation returns true it will have consumed the document representing C# null
- goto setvalue;
+ return null;
}
// handle BSON null for backward compatibility with existing data (new data would have _csharpnull)
if (bsonType == BsonType.Null && (nominalType != typeof(BsonValue) && nominalType != typeof(BsonNull)))
{
bsonReader.ReadNull();
- goto setvalue;
+ return null;
}
}
@@ -505,17 +589,15 @@ private void DeserializeMember(BsonReader bsonReader, object obj, BsonMemberMap
var discriminatorConvention = memberMap.GetDiscriminatorConvention();
actualType = discriminatorConvention.GetActualType(bsonReader, nominalType); // returns nominalType if no discriminator found
}
- var serializer = memberMap.GetSerializer(actualType);
- value = serializer.Deserialize(bsonReader, nominalType, actualType, memberMap.SerializationOptions);
- setvalue:
- memberMap.Setter(obj, value);
+ var serializer = memberMap.GetSerializer(actualType);
+ return serializer.Deserialize(bsonReader, nominalType, actualType, memberMap.SerializationOptions);
}
catch (Exception ex)
{
var message = string.Format(
"An error occurred while deserializing the {0} {1} of class {2}: {3}", // terminating period provided by nested message
- memberMap.MemberName, (memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property", obj.GetType().FullName, ex.Message);
+ memberMap.MemberName, (memberMap.MemberInfo.MemberType == MemberTypes.Field) ? "field" : "property", memberMap.ClassMap.ClassType.FullName, ex.Message);
throw new FileFormatException(message, ex);
}
}
View
536 MongoDB.BsonUnitTests/Jira/CSharp476Tests.cs
@@ -0,0 +1,536 @@
+/* Copyright 2010-2013 10gen 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 MongoDB.Bson;
+using MongoDB.Bson.Serialization;
+using MongoDB.Bson.Serialization.Attributes;
+using NUnit.Framework;
+using System;
+
+namespace MongoDB.BsonUnitTests.Jira
+{
+ [TestFixture]
+ public class CSharp476Tests
+ {
+ // make sure class maps are registered before tests run
+ static CSharp476Tests()
+ {
+ TDelegate.RegisterClassMap();
+ TExpressionCallingConstructor.RegisterClassMap();
+ TExpressionCallingFactoryMethod.RegisterClassMap();
+ TExpressionCallingArbitraryCode.RegisterClassMap();
+ }
+
+ public class TBsonConstructor
+ {
+ private int _chosen;
+ private int _x;
+ private int _y;
+
+ [BsonConstructor]
+ public TBsonConstructor()
+ {
+ _chosen = 0;
+ }
+
+ [BsonConstructor] // let convention find the matching properties
+ public TBsonConstructor(int x)
+ {
+ _chosen = 1;
+ _x = x;
+ }
+
+ [BsonConstructor] // let convention find the matching properties
+ public TBsonConstructor(int x, int y)
+ {
+ _chosen = 2;
+ _x = x;
+ _y = y;
+ }
+
+ [BsonIgnore]
+ public int Chosen { get { return _chosen; } }
+ [BsonElement]
+ public int X { get { return _x; } }
+ [BsonElement]
+ [BsonDefaultValue(2)]
+ public int Y { get { return _y; } }
+ }
+
+ [Test]
+ public void TestTBsonConstructor()
+ {
+ var json = "{ }";
+ var r = BsonSerializer.Deserialize<TBsonConstructor>(json);
+ Assert.AreEqual(0, r.Chosen);
+ Assert.AreEqual(0, r.X);
+ Assert.AreEqual(0, r.Y); // note: unable to apply default value
+
+ json = "{ X : 1 }";
+ r = BsonSerializer.Deserialize<TBsonConstructor>(json);
+ Assert.AreEqual(2, r.Chosen); // passed default value to constructor
+ Assert.AreEqual(1, r.X);
+ Assert.AreEqual(2, r.Y);
+
+ json = "{ X : 1, Y : 3 }";
+ r = BsonSerializer.Deserialize<TBsonConstructor>(json);
+ Assert.AreEqual(2, r.Chosen);
+ Assert.AreEqual(1, r.X);
+ Assert.AreEqual(3, r.Y);
+ }
+
+ public class TBsonFactoryMethod
+ {
+ internal int _chosen;
+ internal int _x;
+ internal int _y;
+
+ [BsonIgnore]
+ public int Chosen { get { return _chosen; } }
+ [BsonElement]
+ public int X { get { return _x; } }
+ [BsonElement]
+ [BsonDefaultValue(2)]
+ public int Y { get { return _y; } }
+
+ [BsonFactoryMethod]
+ public static TBsonFactoryMethod FactoryMethod()
+ {
+ var instance = new TBsonFactoryMethod();
+ instance._chosen = 0;
+ return instance;
+ }
+
+ [BsonFactoryMethod("X")]
+ public static TBsonFactoryMethod FactoryMethod(int x)
+ {
+ var instance = new TBsonFactoryMethod();
+ instance._chosen = 1;
+ instance._x = x;
+ return instance;
+ }
+
+ [BsonFactoryMethod("X", "Y")]
+ public static TBsonFactoryMethod FactoryMethod(int x, int y)
+ {
+ var instance = new TBsonFactoryMethod();
+ instance._chosen = 2;
+ instance._x = x;
+ instance._y = y;
+ return instance;
+ }
+ }
+
+ [Test]
+ public void TestTBsonFactoryMethod()
+ {
+ var json = "{ }";
+ var r = BsonSerializer.Deserialize<TBsonFactoryMethod>(json);
+ Assert.AreEqual(0, r.Chosen);
+ Assert.AreEqual(0, r.X);
+ Assert.AreEqual(0, r.Y); // note: unable to apply default value
+
+ json = "{ X : 1 }";
+ r = BsonSerializer.Deserialize<TBsonFactoryMethod>(json);
+ Assert.AreEqual(2, r.Chosen); // passed default value to factory method
+ Assert.AreEqual(1, r.X);
+ Assert.AreEqual(2, r.Y);
+
+ json = "{ X : 1, Y : 3 }";
+ r = BsonSerializer.Deserialize<TBsonFactoryMethod>(json);
+ Assert.AreEqual(2, r.Chosen);
+ Assert.AreEqual(1, r.X);
+ Assert.AreEqual(3, r.Y);
+ }
+
+ public class TDelegate
+ {
+ private int _chosen;
+ private int _x;
+ private int _y;
+
+ public TDelegate()
+ {
+ _chosen = 0;
+ }
+
+ public TDelegate(int x)
+ {
+ _chosen = 1;
+ _x = x;
+ }
+
+ public TDelegate(int x, int y)
+ {
+ _chosen = 2;
+ _x = x;
+ _y = y;
+ }
+
+ [BsonIgnore]
+ public int Chosen { get { return _chosen; } }
+ [BsonElement]
+ public int X { get { return _x; } }
+ [BsonElement]
+ [BsonDefaultValue(2)]
+ public int Y { get { return _y; } }
+
+ public static void RegisterClassMap()
+ {
+ BsonClassMap.RegisterClassMap<TDelegate>(cm =>
+ {
+ cm.AutoMap();
+ cm.MapCreator((Func<TDelegate>)(() => new TDelegate()));
+ cm.MapCreator((Func<int, TDelegate>)((int x) => new TDelegate(x)), "X");
+ cm.MapCreator((Func<int, int, TDelegate>)((int x, int y) => new TDelegate(x, y)), "X", "Y");
+ });
+ }
+ }
+
+ [Test]
+ public void TestTDelegate()
+ {
+ var json = "{ }";
+ var r = BsonSerializer.Deserialize<TDelegate>(json);
+ Assert.AreEqual(0, r.Chosen);
+ Assert.AreEqual(0, r.X);
+ Assert.AreEqual(0, r.Y); // note: unable to apply default value
+
+ json = "{ X : 1 }";
+ r = BsonSerializer.Deserialize<TDelegate>(json);
+ Assert.AreEqual(2, r.Chosen); // passed default value to delegate
+ Assert.AreEqual(1, r.X);
+ Assert.AreEqual(2, r.Y);
+
+ json = "{ X : 1, Y : 3 }";
+ r = BsonSerializer.Deserialize<TDelegate>(json);
+ Assert.AreEqual(2, r.Chosen);
+ Assert.AreEqual(1, r.X);
+ Assert.AreEqual(3, r.Y);
+ }
+
+ public class TExpressionCallingConstructor
+ {
+ private int _chosen;
+ private int _x;
+ private int _y;
+
+ public TExpressionCallingConstructor()
+ {
+ _chosen = 0;
+ }
+
+ public TExpressionCallingConstructor(int x)
+ {
+ _chosen = 1;
+ _x = x;
+ }
+
+ public TExpressionCallingConstructor(int x, int y)
+ {
+ _chosen = 2;
+ _x = x;
+ _y = y;
+ }
+
+ [BsonIgnore]
+ public int Chosen { get { return _chosen; } }
+ [BsonElement]
+ public int X { get { return _x; } }
+ [BsonElement]
+ [BsonDefaultValue(2)]
+ public int Y { get { return _y; } }
+
+ public static void RegisterClassMap()
+ {
+ BsonClassMap.RegisterClassMap<TExpressionCallingConstructor>(cm =>
+ {
+ cm.AutoMap();
+ cm.MapCreator(c => new TExpressionCallingConstructor());
+ cm.MapCreator(c => new TExpressionCallingConstructor(c.X));
+ cm.MapCreator(c => new TExpressionCallingConstructor(c.X, c.Y));
+ });
+ }
+ }
+
+ [Test]
+ public void TestTExpressionCallingConstructor()
+ {
+ var json = "{ }";
+ var r = BsonSerializer.Deserialize<TExpressionCallingConstructor>(json);