/* Copyright 2010-2016 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.Collections.ObjectModel; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; #if NET45 using System.Runtime.Serialization; #endif using MongoDB.Bson.IO; using MongoDB.Bson.Serialization.Conventions; namespace MongoDB.Bson.Serialization { /// /// Represents a mapping between a class and a BSON document. /// public class BsonClassMap { // private static fields private readonly static Dictionary __classMaps = new Dictionary(); private readonly static Queue __knownTypesQueue = new Queue(); private static readonly MethodInfo __getUninitializedObjectMethodInfo = typeof(string) .GetTypeInfo() .Assembly .GetType("System.Runtime.Serialization.FormatterServices") .GetTypeInfo() ?.GetMethod("GetUninitializedObject", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static); private static int __freezeNestingLevel = 0; // private fields private readonly Type _classType; private readonly List _creatorMaps; private readonly IConventionPack _conventionPack; private readonly bool _isAnonymous; private readonly List _allMemberMaps; // includes inherited member maps private readonly ReadOnlyCollection _allMemberMapsReadonly; private readonly List _declaredMemberMaps; // only the members declared in this class private readonly BsonTrie _elementTrie; private bool _frozen; // once a class map has been frozen no further changes are allowed private BsonClassMap _baseClassMap; // null for class object and interfaces private volatile IDiscriminatorConvention _discriminatorConvention; private Func _creator; private string _discriminator; private bool _discriminatorIsRequired; private bool _hasRootClass; private bool _isRootClass; private BsonMemberMap _idMemberMap; private bool _ignoreExtraElements; private bool _ignoreExtraElementsIsInherited; private BsonMemberMap _extraElementsMemberMap; private int _extraElementsMemberIndex = -1; private List _knownTypes = new List(); // constructors /// /// Initializes a new instance of the BsonClassMap class. /// /// The class type. public BsonClassMap(Type classType) { _classType = classType; _creatorMaps = new List(); _conventionPack = ConventionRegistry.Lookup(classType); _isAnonymous = IsAnonymousType(classType); _allMemberMaps = new List(); _allMemberMapsReadonly = _allMemberMaps.AsReadOnly(); _declaredMemberMaps = new List(); _elementTrie = new BsonTrie(); Reset(); } /// /// Initializes a new instance of the class. /// /// Type of the class. /// The base class map. public BsonClassMap(Type classType, BsonClassMap baseClassMap) : this(classType) { _baseClassMap = baseClassMap; } // public properties /// /// Gets all the member maps (including maps for inherited members). /// public ReadOnlyCollection AllMemberMaps { get { return _allMemberMapsReadonly; } } /// /// Gets the base class map. /// public BsonClassMap BaseClassMap { get { return _baseClassMap; } } /// /// Gets the class type. /// public Type ClassType { get { return _classType; } } /// /// Gets the constructor maps. /// public IEnumerable CreatorMaps { get { return _creatorMaps; } } /// /// Gets the conventions used for auto mapping. /// public IConventionPack ConventionPack { get { return _conventionPack; } } /// /// Gets the declared member maps (only for members declared in this class). /// public IEnumerable DeclaredMemberMaps { get { return _declaredMemberMaps; } } /// /// Gets the discriminator. /// public string Discriminator { get { return _discriminator; } } /// /// Gets whether a discriminator is required when serializing this class. /// public bool DiscriminatorIsRequired { get { return _discriminatorIsRequired; } } /// /// Gets the member map of the member used to hold extra elements. /// public BsonMemberMap ExtraElementsMemberMap { get { return _extraElementsMemberMap; } } /// /// Gets whether this class map has any creator maps. /// public bool HasCreatorMaps { get { return _creatorMaps.Count > 0; } } /// /// Gets whether this class has a root class ancestor. /// public bool HasRootClass { get { return _hasRootClass; } } /// /// Gets the Id member map (null if none). /// public BsonMemberMap IdMemberMap { get { return _idMemberMap; } } /// /// Gets whether extra elements should be ignored when deserializing. /// public bool IgnoreExtraElements { get { return _ignoreExtraElements; } } /// /// Gets whether the IgnoreExtraElements value should be inherited by derived classes. /// public bool IgnoreExtraElementsIsInherited { get { return _ignoreExtraElementsIsInherited; } } /// /// Gets whether this class is anonymous. /// public bool IsAnonymous { get { return _isAnonymous; } } /// /// Gets whether the class map is frozen. /// public bool IsFrozen { get { return _frozen; } } /// /// Gets whether this class is a root class. /// public bool IsRootClass { get { return _isRootClass; } } /// /// Gets the known types of this class. /// public IEnumerable KnownTypes { get { return _knownTypes; } } // internal properties /// /// Gets the element name to member index trie. /// internal BsonTrie ElementTrie { get { return _elementTrie; } } /// /// Gets the member index of the member used to hold extra elements. /// internal int ExtraElementsMemberMapIndex { get { return _extraElementsMemberIndex; } } // public static methods /// /// Gets the type of a member. /// /// The member info. /// The type of the member. public static Type GetMemberInfoType(MemberInfo memberInfo) { if (memberInfo == null) { throw new ArgumentNullException("memberInfo"); } if (memberInfo is FieldInfo) { return ((FieldInfo)memberInfo).FieldType; } else if (memberInfo is PropertyInfo) { return ((PropertyInfo)memberInfo).PropertyType; } throw new NotSupportedException("Only field and properties are supported at this time."); } /// /// Gets all registered class maps. /// /// All registered class maps. public static IEnumerable GetRegisteredClassMaps() { BsonSerializer.ConfigLock.EnterReadLock(); try { return __classMaps.Values.ToList(); // return a copy for thread safety } finally { BsonSerializer.ConfigLock.ExitReadLock(); } } /// /// Checks whether a class map is registered for a type. /// /// The type to check. /// True if there is a class map registered for the type. public static bool IsClassMapRegistered(Type type) { if (type == null) { throw new ArgumentNullException("type"); } BsonSerializer.ConfigLock.EnterReadLock(); try { return __classMaps.ContainsKey(type); } finally { BsonSerializer.ConfigLock.ExitReadLock(); } } /// /// Looks up a class map (will AutoMap the class if no class map is registered). /// /// The class type. /// The class map. public static BsonClassMap LookupClassMap(Type classType) { if (classType == null) { throw new ArgumentNullException("classType"); } BsonSerializer.ConfigLock.EnterReadLock(); try { BsonClassMap classMap; if (__classMaps.TryGetValue(classType, out classMap)) { if (classMap.IsFrozen) { return classMap; } } } finally { BsonSerializer.ConfigLock.ExitReadLock(); } BsonSerializer.ConfigLock.EnterWriteLock(); try { BsonClassMap classMap; if (!__classMaps.TryGetValue(classType, out classMap)) { // automatically create a classMap for classType and register it var classMapDefinition = typeof(BsonClassMap<>); var classMapType = classMapDefinition.MakeGenericType(classType); classMap = (BsonClassMap)Activator.CreateInstance(classMapType); classMap.AutoMap(); RegisterClassMap(classMap); } return classMap.Freeze(); } finally { BsonSerializer.ConfigLock.ExitWriteLock(); } } /// /// Creates and registers a class map. /// /// The class. /// The class map. public static BsonClassMap RegisterClassMap() { return RegisterClassMap(cm => { cm.AutoMap(); }); } /// /// Creates and registers a class map. /// /// The class. /// The class map initializer. /// The class map. public static BsonClassMap RegisterClassMap(Action> classMapInitializer) { var classMap = new BsonClassMap(classMapInitializer); RegisterClassMap(classMap); return classMap; } /// /// Registers a class map. /// /// The class map. public static void RegisterClassMap(BsonClassMap classMap) { if (classMap == null) { throw new ArgumentNullException("classMap"); } BsonSerializer.ConfigLock.EnterWriteLock(); try { // note: class maps can NOT be replaced (because derived classes refer to existing instance) __classMaps.Add(classMap.ClassType, classMap); BsonSerializer.RegisterDiscriminator(classMap.ClassType, classMap.Discriminator); } finally { BsonSerializer.ConfigLock.ExitWriteLock(); } } // public methods /// /// Automaps the class. /// public void AutoMap() { if (_frozen) { ThrowFrozenException(); } AutoMapClass(); } /// /// Creates an instance of the class. /// /// An object. public object CreateInstance() { if (!_frozen) { ThrowNotFrozenException(); } var creator = GetCreator(); return creator.Invoke(); } /// /// Freezes the class map. /// /// The frozen class map. public BsonClassMap Freeze() { BsonSerializer.ConfigLock.EnterReadLock(); try { if (_frozen) { return this; } } finally { BsonSerializer.ConfigLock.ExitReadLock(); } BsonSerializer.ConfigLock.EnterWriteLock(); try { if (!_frozen) { __freezeNestingLevel++; try { var baseType = _classType.GetTypeInfo().BaseType; if (baseType != null) { if (_baseClassMap == null) { _baseClassMap = LookupClassMap(baseType); } _discriminatorIsRequired |= _baseClassMap._discriminatorIsRequired; _hasRootClass |= (_isRootClass || _baseClassMap.HasRootClass); _allMemberMaps.AddRange(_baseClassMap.AllMemberMaps); if (_baseClassMap.IgnoreExtraElements && _baseClassMap.IgnoreExtraElementsIsInherited) { _ignoreExtraElements = true; _ignoreExtraElementsIsInherited = true; } } _allMemberMaps.AddRange(_declaredMemberMaps); if (_idMemberMap == null) { // see if we can inherit the idMemberMap from our base class if (_baseClassMap != null) { _idMemberMap = _baseClassMap.IdMemberMap; } } else { if (_idMemberMap.ClassMap == this) { // conventions could have set this to an improper value _idMemberMap.SetElementName("_id"); } } if (_extraElementsMemberMap == null) { // see if we can inherit the extraElementsMemberMap from our base class if (_baseClassMap != null) { _extraElementsMemberMap = _baseClassMap.ExtraElementsMemberMap; } } _extraElementsMemberIndex = -1; for (int memberIndex = 0; memberIndex < _allMemberMaps.Count; ++memberIndex) { var memberMap = _allMemberMaps[memberIndex]; int conflictingMemberIndex; if (!_elementTrie.TryGetValue(memberMap.ElementName, out conflictingMemberIndex)) { _elementTrie.Add(memberMap.ElementName, memberIndex); } else { var conflictingMemberMap = _allMemberMaps[conflictingMemberIndex]; var fieldOrProperty = (memberMap.MemberInfo is FieldInfo) ? "field" : "property"; var conflictingFieldOrProperty = (conflictingMemberMap.MemberInfo is FieldInfo) ? "field" : "property"; var conflictingType = conflictingMemberMap.MemberInfo.DeclaringType; string message; if (conflictingType == _classType) { message = string.Format( "The {0} '{1}' of type '{2}' cannot use element name '{3}' because it is already being used by {4} '{5}'.", fieldOrProperty, memberMap.MemberName, _classType.FullName, memberMap.ElementName, conflictingFieldOrProperty, conflictingMemberMap.MemberName); } else { message = string.Format( "The {0} '{1}' of type '{2}' cannot use element name '{3}' because it is already being used by {4} '{5}' of type '{6}'.", fieldOrProperty, memberMap.MemberName, _classType.FullName, memberMap.ElementName, conflictingFieldOrProperty, conflictingMemberMap.MemberName, conflictingType.FullName); } throw new BsonSerializationException(message); } if (memberMap == _extraElementsMemberMap) { _extraElementsMemberIndex = memberIndex; } } // mark this classMap frozen before we start working on knownTypes // because we might get back to this same classMap while processing knownTypes foreach (var creatorMap in _creatorMaps) { creatorMap.Freeze(); } foreach (var memberMap in _declaredMemberMaps) { memberMap.Freeze(); } _frozen = true; // 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) { __knownTypesQueue.Enqueue(knownType); } // if we are back to the first level go ahead and process any queued known types if (__freezeNestingLevel == 1) { while (__knownTypesQueue.Count != 0) { var knownType = __knownTypesQueue.Dequeue(); LookupClassMap(knownType); // will AutoMap and/or Freeze knownType if necessary } } } finally { __freezeNestingLevel--; } } } finally { BsonSerializer.ConfigLock.ExitWriteLock(); } return this; } /// /// Gets a member map (only considers members declared in this class). /// /// The member name. /// The member map (or null if the member was not found). public BsonMemberMap GetMemberMap(string memberName) { if (memberName == null) { throw new ArgumentNullException("memberName"); } // can be called whether frozen or not return _declaredMemberMaps.Find(m => m.MemberName == memberName); } /// /// Gets the member map for a BSON element. /// /// The name of the element. /// The member map. public BsonMemberMap GetMemberMapForElement(string elementName) { if (elementName == null) { throw new ArgumentNullException("elementName"); } if (!_frozen) { ThrowNotFrozenException(); } int memberIndex; if (!_elementTrie.TryGetValue(elementName, out memberIndex)) { return null; } var memberMap = _allMemberMaps[memberIndex]; return memberMap; } /// /// Creates a creator map for a constructor and adds it to the class map. /// /// The constructor info. /// The creator map (so method calls can be chained). 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; } /// /// Creates a creator map for a constructor and adds it to the class map. /// /// The constructor info. /// The argument names. /// The creator map (so method calls can be chained). public BsonCreatorMap MapConstructor(ConstructorInfo constructorInfo, params string[] argumentNames) { var creatorMap = MapConstructor(constructorInfo); creatorMap.SetArguments(argumentNames); return creatorMap; } /// /// Creates a creator map and adds it to the class. /// /// The delegate. /// The factory method map (so method calls can be chained). 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; } /// /// Creates a creator map and adds it to the class. /// /// The delegate. /// The argument names. /// The factory method map (so method calls can be chained). public BsonCreatorMap MapCreator(Delegate @delegate, params string[] argumentNames) { var creatorMap = MapCreator(@delegate); creatorMap.SetArguments(argumentNames); return creatorMap; } /// /// Creates a member map for the extra elements field and adds it to the class map. /// /// The name of the extra elements field. /// The member map (so method calls can be chained). public BsonMemberMap MapExtraElementsField(string fieldName) { if (fieldName == null) { throw new ArgumentNullException("fieldName"); } if (_frozen) { ThrowFrozenException(); } var fieldMap = MapField(fieldName); SetExtraElementsMember(fieldMap); return fieldMap; } /// /// Creates a member map for the extra elements member and adds it to the class map. /// /// The member info for the extra elements member. /// The member map (so method calls can be chained). public BsonMemberMap MapExtraElementsMember(MemberInfo memberInfo) { if (memberInfo == null) { throw new ArgumentNullException("memberInfo"); } if (_frozen) { ThrowFrozenException(); } var memberMap = MapMember(memberInfo); SetExtraElementsMember(memberMap); return memberMap; } /// /// Creates a member map for the extra elements property and adds it to the class map. /// /// The name of the property. /// The member map (so method calls can be chained). public BsonMemberMap MapExtraElementsProperty(string propertyName) { if (propertyName == null) { throw new ArgumentNullException("propertyName"); } if (_frozen) { ThrowFrozenException(); } var propertyMap = MapProperty(propertyName); SetExtraElementsMember(propertyMap); return propertyMap; } /// /// Creates a creator map for a factory method and adds it to the class. /// /// The method info. /// The creator map (so method calls can be chained). 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; } /// /// Creates a creator map for a factory method and adds it to the class. /// /// The method info. /// The argument names. /// The creator map (so method calls can be chained). public BsonCreatorMap MapFactoryMethod(MethodInfo methodInfo, params string[] argumentNames) { var creatorMap = MapFactoryMethod(methodInfo); creatorMap.SetArguments(argumentNames); return creatorMap; } /// /// Creates a member map for a field and adds it to the class map. /// /// The name of the field. /// The member map (so method calls can be chained). public BsonMemberMap MapField(string fieldName) { if (fieldName == null) { throw new ArgumentNullException("fieldName"); } if (_frozen) { ThrowFrozenException(); } var fieldInfo = _classType.GetTypeInfo().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (fieldInfo == null) { var message = string.Format("The class '{0}' does not have a field named '{1}'.", _classType.FullName, fieldName); throw new BsonSerializationException(message); } return MapMember(fieldInfo); } /// /// Creates a member map for the Id field and adds it to the class map. /// /// The name of the Id field. /// The member map (so method calls can be chained). public BsonMemberMap MapIdField(string fieldName) { if (fieldName == null) { throw new ArgumentNullException("fieldName"); } if (_frozen) { ThrowFrozenException(); } var fieldMap = MapField(fieldName); SetIdMember(fieldMap); return fieldMap; } /// /// Creates a member map for the Id member and adds it to the class map. /// /// The member info for the Id member. /// The member map (so method calls can be chained). public BsonMemberMap MapIdMember(MemberInfo memberInfo) { if (memberInfo == null) { throw new ArgumentNullException("memberInfo"); } if (_frozen) { ThrowFrozenException(); } var memberMap = MapMember(memberInfo); SetIdMember(memberMap); return memberMap; } /// /// Creates a member map for the Id property and adds it to the class map. /// /// The name of the Id property. /// The member map (so method calls can be chained). public BsonMemberMap MapIdProperty(string propertyName) { if (propertyName == null) { throw new ArgumentNullException("propertyName"); } if (_frozen) { ThrowFrozenException(); } var propertyMap = MapProperty(propertyName); SetIdMember(propertyMap); return propertyMap; } /// /// Creates a member map for a member and adds it to the class map. /// /// The member info. /// The member map (so method calls can be chained). public BsonMemberMap MapMember(MemberInfo memberInfo) { if (memberInfo == null) { 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(); } var memberMap = _declaredMemberMaps.Find(m => m.MemberInfo == memberInfo); if (memberMap == null) { memberMap = new BsonMemberMap(this, memberInfo); _declaredMemberMaps.Add(memberMap); } return memberMap; } /// /// Creates a member map for a property and adds it to the class map. /// /// The name of the property. /// The member map (so method calls can be chained). public BsonMemberMap MapProperty(string propertyName) { if (propertyName == null) { throw new ArgumentNullException("propertyName"); } if (_frozen) { ThrowFrozenException(); } var propertyInfo = _classType.GetTypeInfo().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (propertyInfo == null) { var message = string.Format("The class '{0}' does not have a property named '{1}'.", _classType.FullName, propertyName); throw new BsonSerializationException(message); } return MapMember(propertyInfo); } /// /// Resets the class map back to its initial state. /// public void Reset() { if (_frozen) { ThrowFrozenException(); } _creatorMaps.Clear(); _creator = null; _declaredMemberMaps.Clear(); _discriminator = _classType.Name; _discriminatorIsRequired = false; _extraElementsMemberMap = null; _idMemberMap = null; _ignoreExtraElements = true; // TODO: should this really be false? _ignoreExtraElementsIsInherited = false; _isRootClass = false; _knownTypes.Clear(); } /// /// Sets the creator for the object. /// /// The creator. /// The class map (so method calls can be chained). public BsonClassMap SetCreator(Func creator) { _creator = creator; return this; } /// /// Sets the discriminator. /// /// The discriminator. public void SetDiscriminator(string discriminator) { if (discriminator == null) { throw new ArgumentNullException("discriminator"); } if (_frozen) { ThrowFrozenException(); } _discriminator = discriminator; } /// /// Sets whether a discriminator is required when serializing this class. /// /// Whether a discriminator is required. public void SetDiscriminatorIsRequired(bool discriminatorIsRequired) { if (_frozen) { ThrowFrozenException(); } _discriminatorIsRequired = discriminatorIsRequired; } /// /// Sets the member map of the member used to hold extra elements. /// /// The extra elements member map. public void SetExtraElementsMember(BsonMemberMap memberMap) { if (memberMap == null) { throw new ArgumentNullException("memberMap"); } EnsureMemberMapIsForThisClass(memberMap); if (_frozen) { ThrowFrozenException(); } if (memberMap.MemberType != typeof(BsonDocument) && !typeof(IDictionary).GetTypeInfo().IsAssignableFrom(memberMap.MemberType)) { var message = string.Format("Type of ExtraElements member must be BsonDocument or implement IDictionary."); throw new InvalidOperationException(message); } _extraElementsMemberMap = memberMap; } /// /// Adds a known type to the class map. /// /// The known type. public void AddKnownType(Type type) { if (!_classType.GetTypeInfo().IsAssignableFrom(type)) { string message = string.Format("Class {0} cannot be assigned to Class {1}. Ensure that known types are derived from the mapped class.", type.FullName, _classType.FullName); throw new ArgumentNullException("type", message); } if (_frozen) { ThrowFrozenException(); } _knownTypes.Add(type); } /// /// Sets the Id member. /// /// The Id member (null if none). public void SetIdMember(BsonMemberMap memberMap) { if (memberMap != null) { EnsureMemberMapIsForThisClass(memberMap); } if (_frozen) { ThrowFrozenException(); } _idMemberMap = memberMap; } /// /// Sets whether extra elements should be ignored when deserializing. /// /// Whether extra elements should be ignored when deserializing. public void SetIgnoreExtraElements(bool ignoreExtraElements) { if (_frozen) { ThrowFrozenException(); } _ignoreExtraElements = ignoreExtraElements; } /// /// Sets whether the IgnoreExtraElements value should be inherited by derived classes. /// /// Whether the IgnoreExtraElements value should be inherited by derived classes. public void SetIgnoreExtraElementsIsInherited(bool ignoreExtraElementsIsInherited) { if (_frozen) { ThrowFrozenException(); } _ignoreExtraElementsIsInherited = ignoreExtraElementsIsInherited; } /// /// Sets whether this class is a root class. /// /// Whether this class is a root class. public void SetIsRootClass(bool isRootClass) { if (_frozen) { ThrowFrozenException(); } _isRootClass = isRootClass; } /// /// Removes a creator map for a constructor from the class map. /// /// The constructor info. 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); } } /// /// Removes a creator map for a factory method from the class map. /// /// The method info. 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); } } /// /// Removes the member map for a field from the class map. /// /// The name of the field. public void UnmapField(string fieldName) { if (fieldName == null) { throw new ArgumentNullException("fieldName"); } if (_frozen) { ThrowFrozenException(); } var fieldInfo = _classType.GetTypeInfo().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (fieldInfo == null) { var message = string.Format("The class '{0}' does not have a field named '{1}'.", _classType.FullName, fieldName); throw new BsonSerializationException(message); } UnmapMember(fieldInfo); } /// /// Removes a member map from the class map. /// /// The member info. public void UnmapMember(MemberInfo memberInfo) { if (memberInfo == null) { throw new ArgumentNullException("memberInfo"); } EnsureMemberInfoIsForThisClass(memberInfo); if (_frozen) { ThrowFrozenException(); } var memberMap = _declaredMemberMaps.Find(m => m.MemberInfo == memberInfo); if (memberMap != null) { _declaredMemberMaps.Remove(memberMap); if (_idMemberMap == memberMap) { _idMemberMap = null; } if (_extraElementsMemberMap == memberMap) { _extraElementsMemberMap = null; } } } /// /// Removes the member map for a property from the class map. /// /// The name of the property. public void UnmapProperty(string propertyName) { if (propertyName == null) { throw new ArgumentNullException("propertyName"); } if (_frozen) { ThrowFrozenException(); } var propertyInfo = _classType.GetTypeInfo().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); if (propertyInfo == null) { var message = string.Format("The class '{0}' does not have a property named '{1}'.", _classType.FullName, propertyName); throw new BsonSerializationException(message); } UnmapMember(propertyInfo); } // internal methods /// /// Gets the discriminator convention for the class. /// /// The discriminator convention for the class. internal IDiscriminatorConvention GetDiscriminatorConvention() { // return a cached discriminator convention when possible var discriminatorConvention = _discriminatorConvention; if (discriminatorConvention == null) { // it's possible but harmless for multiple threads to do the initial lookup at the same time discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(_classType); _discriminatorConvention = discriminatorConvention; } return discriminatorConvention; } // private methods private void AutoMapClass() { new ConventionRunner(_conventionPack).Apply(this); OrderMembers(); foreach (var memberMap in _declaredMemberMaps) { TryFindShouldSerializeMethod(memberMap); } } private void OrderMembers() { // only auto map properties declared in this class (and not in base classes) var hasOrderedElements = false; var hasUnorderedElements = false; foreach (var memberMap in _declaredMemberMaps) { if (memberMap.Order != int.MaxValue) { hasOrderedElements |= true; } else { hasUnorderedElements |= true; } } if (hasOrderedElements) { if (hasUnorderedElements) { // split out the unordered elements and add them back at the end (because Sort is unstable, see online help) var unorderedElements = new List(_declaredMemberMaps.Where(pm => pm.Order == int.MaxValue)); _declaredMemberMaps.RemoveAll(m => m.Order == int.MaxValue); _declaredMemberMaps.Sort((x, y) => x.Order.CompareTo(y.Order)); _declaredMemberMaps.AddRange(unorderedElements); } else { _declaredMemberMaps.Sort((x, y) => x.Order.CompareTo(y.Order)); } } } private void TryFindShouldSerializeMethod(BsonMemberMap memberMap) { // see if the class has a method called ShouldSerializeXyz where Xyz is the name of this member var shouldSerializeMethod = GetShouldSerializeMethod(memberMap.MemberInfo); if (shouldSerializeMethod != null) { memberMap.SetShouldSerializeMethod(shouldSerializeMethod); } } private void EnsureMemberInfoIsForThisClass(MemberInfo memberInfo) { if (memberInfo.DeclaringType != _classType) { var message = string.Format( "The memberInfo argument must be for class {0}, but was for class {1}.", _classType.Name, memberInfo.DeclaringType.Name); throw new ArgumentOutOfRangeException("memberInfo", message); } } private void EnsureMemberMapIsForThisClass(BsonMemberMap memberMap) { if (memberMap.ClassMap != this) { var message = string.Format( "The memberMap argument must be for class {0}, but was for class {1}.", _classType.Name, memberMap.ClassMap.ClassType.Name); throw new ArgumentOutOfRangeException("memberMap", message); } } private Func GetCreator() { if (_creator == null) { Expression body; var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var classTypeInfo = _classType.GetTypeInfo(); ConstructorInfo defaultConstructor = classTypeInfo.GetConstructors(bindingFlags) .Where(c => c.GetParameters().Length == 0) .SingleOrDefault(); #if ENABLE_IL2CPP _creator = () => defaultConstructor.Invoke(null); #else if (defaultConstructor != null) { // lambdaExpression = () => (object) new TClass() body = Expression.New(defaultConstructor); } else if (__getUninitializedObjectMethodInfo != null) { // lambdaExpression = () => FormatterServices.GetUninitializedObject(classType) body = Expression.Call(__getUninitializedObjectMethodInfo, Expression.Constant(_classType)); } else { var message = $"Type '{_classType.GetType().Name}' does not have a default constructor."; throw new BsonSerializationException(message); } var lambdaExpression = Expression.Lambda>(body); _creator = lambdaExpression.Compile(); #endif } return _creator; } private Func GetShouldSerializeMethod(MemberInfo memberInfo) { var shouldSerializeMethodName = "ShouldSerialize" + memberInfo.Name; var shouldSerializeMethodInfo = _classType.GetTypeInfo().GetMethod(shouldSerializeMethodName, new Type[] { }); if (shouldSerializeMethodInfo != null && shouldSerializeMethodInfo.IsPublic && shouldSerializeMethodInfo.ReturnType == typeof(bool)) { // lambdaExpression = (obj) => ((TClass) obj).ShouldSerializeXyz() var objParameter = Expression.Parameter(typeof(object), "obj"); var lambdaExpression = Expression.Lambda>(Expression.Call(Expression.Convert(objParameter, _classType), shouldSerializeMethodInfo), objParameter); return lambdaExpression.Compile(); } else { return null; } } private bool IsAnonymousType(Type type) { // don't test for too many things in case implementation details change in the future var typeInfo = type.GetTypeInfo(); return typeInfo.GetCustomAttributes(false).Any() && typeInfo.IsGenericType && type.Name.Contains("Anon"); // don't check for more than "Anon" so it works in mono also } private void ThrowFrozenException() { var message = string.Format("Class map for {0} has been frozen and no further changes are allowed.", _classType.FullName); throw new InvalidOperationException(message); } private void ThrowNotFrozenException() { var message = string.Format("Class map for {0} has been not been frozen yet.", _classType.FullName); throw new InvalidOperationException(message); } } /// /// Represents a mapping between a class and a BSON document. /// /// The class. public class BsonClassMap : BsonClassMap { // constructors /// /// Initializes a new instance of the BsonClassMap class. /// public BsonClassMap() : base(typeof(TClass)) { } /// /// Initializes a new instance of the BsonClassMap class. /// /// The class map initializer. public BsonClassMap(Action> classMapInitializer) : base(typeof(TClass)) { classMapInitializer(this); } // public methods /// /// Creates an instance. /// /// An instance. public new TClass CreateInstance() { return (TClass)base.CreateInstance(); } /// /// Gets a member map. /// /// The member type. /// A lambda expression specifying the member. /// The member map. public BsonMemberMap GetMemberMap(Expression> memberLambda) { var memberName = GetMemberNameFromLambda(memberLambda); return GetMemberMap(memberName); } /// /// Creates a creator map and adds it to the class map. /// /// Lambda expression specifying the creator code and parameters to use. /// The member map. public BsonCreatorMap MapCreator(Expression> creatorLambda) { if (creatorLambda == null) { throw new ArgumentNullException("creatorLambda"); } IEnumerable arguments; var @delegate = new CreatorMapDelegateCompiler().CompileCreatorDelegate(creatorLambda, out arguments); var creatorMap = MapCreator(@delegate); creatorMap.SetArguments(arguments); return creatorMap; } /// /// Creates a member map for the extra elements field and adds it to the class map. /// /// The member type. /// A lambda expression specifying the extra elements field. /// The member map. public BsonMemberMap MapExtraElementsField(Expression> fieldLambda) { var fieldMap = MapField(fieldLambda); SetExtraElementsMember(fieldMap); return fieldMap; } /// /// Creates a member map for the extra elements member and adds it to the class map. /// /// The member type. /// A lambda expression specifying the extra elements member. /// The member map. public BsonMemberMap MapExtraElementsMember(Expression> memberLambda) { var memberMap = MapMember(memberLambda); SetExtraElementsMember(memberMap); return memberMap; } /// /// Creates a member map for the extra elements property and adds it to the class map. /// /// The member type. /// A lambda expression specifying the extra elements property. /// The member map. public BsonMemberMap MapExtraElementsProperty(Expression> propertyLambda) { var propertyMap = MapProperty(propertyLambda); SetExtraElementsMember(propertyMap); return propertyMap; } /// /// Creates a member map for a field and adds it to the class map. /// /// The member type. /// A lambda expression specifying the field. /// The member map. public BsonMemberMap MapField(Expression> fieldLambda) { return MapMember(fieldLambda); } /// /// Creates a member map for the Id field and adds it to the class map. /// /// The member type. /// A lambda expression specifying the Id field. /// The member map. public BsonMemberMap MapIdField(Expression> fieldLambda) { var fieldMap = MapField(fieldLambda); SetIdMember(fieldMap); return fieldMap; } /// /// Creates a member map for the Id member and adds it to the class map. /// /// The member type. /// A lambda expression specifying the Id member. /// The member map. public BsonMemberMap MapIdMember(Expression> memberLambda) { var memberMap = MapMember(memberLambda); SetIdMember(memberMap); return memberMap; } /// /// Creates a member map for the Id property and adds it to the class map. /// /// The member type. /// A lambda expression specifying the Id property. /// The member map. public BsonMemberMap MapIdProperty(Expression> propertyLambda) { var propertyMap = MapProperty(propertyLambda); SetIdMember(propertyMap); return propertyMap; } /// /// Creates a member map and adds it to the class map. /// /// The member type. /// A lambda expression specifying the member. /// The member map. public BsonMemberMap MapMember(Expression> memberLambda) { var memberInfo = GetMemberInfoFromLambda(memberLambda); return MapMember(memberInfo); } /// /// Creates a member map for the Id property and adds it to the class map. /// /// The member type. /// A lambda expression specifying the Id property. /// The member map. public BsonMemberMap MapProperty(Expression> propertyLambda) { return MapMember(propertyLambda); } /// /// Removes the member map for a field from the class map. /// /// The member type. /// A lambda expression specifying the field. public void UnmapField(Expression> fieldLambda) { UnmapMember(fieldLambda); } /// /// Removes a member map from the class map. /// /// The member type. /// A lambda expression specifying the member. public void UnmapMember(Expression> memberLambda) { var memberInfo = GetMemberInfoFromLambda(memberLambda); UnmapMember(memberInfo); } /// /// Removes a member map for a property from the class map. /// /// The member type. /// A lambda expression specifying the property. public void UnmapProperty(Expression> propertyLambda) { UnmapMember(propertyLambda); } // private static methods private static MethodInfo[] GetPropertyAccessors(PropertyInfo propertyInfo) { return propertyInfo.GetAccessors(true); } private static MemberInfo GetMemberInfoFromLambda(Expression> memberLambda) { var body = memberLambda.Body; MemberExpression memberExpression; switch (body.NodeType) { case ExpressionType.MemberAccess: memberExpression = (MemberExpression)body; break; case ExpressionType.Convert: var convertExpression = (UnaryExpression)body; memberExpression = (MemberExpression)convertExpression.Operand; break; default: throw new BsonSerializationException("Invalid lambda expression"); } var memberInfo = memberExpression.Member; if (memberInfo is PropertyInfo) { if (memberInfo.DeclaringType.GetTypeInfo().IsInterface) { memberInfo = FindPropertyImplementation((PropertyInfo)memberInfo, typeof(TClass)); } } else if (!(memberInfo is FieldInfo)) { throw new BsonSerializationException("Invalid lambda expression"); } return memberInfo; } private static string GetMemberNameFromLambda(Expression> memberLambda) { return GetMemberInfoFromLambda(memberLambda).Name; } private static PropertyInfo FindPropertyImplementation(PropertyInfo interfacePropertyInfo, Type actualType) { var interfaceType = interfacePropertyInfo.DeclaringType; #if NETSTANDARD1_5 || NETSTANDARD1_6 var actualTypeInfo = actualType.GetTypeInfo(); var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; var actualTypePropertyInfos = actualTypeInfo.GetMembers(bindingFlags).OfType(); var explicitlyImplementedPropertyName = $"{interfacePropertyInfo.DeclaringType.FullName}.{interfacePropertyInfo.Name}".Replace("+", "."); var explicitlyImplementedPropertyInfo = actualTypePropertyInfos .Where(p => p.Name == explicitlyImplementedPropertyName) .SingleOrDefault(); if (explicitlyImplementedPropertyInfo != null) { return explicitlyImplementedPropertyInfo; } var implicitlyImplementedPropertyInfo = actualTypePropertyInfos .Where(p => p.Name == interfacePropertyInfo.Name && p.PropertyType == interfacePropertyInfo.PropertyType) .SingleOrDefault(); if (implicitlyImplementedPropertyInfo != null) { return implicitlyImplementedPropertyInfo; } throw new BsonSerializationException($"Unable to find property info for property: '{interfacePropertyInfo.Name}'."); #else // An interface map must be used because because there is no // other officially documented way to derive the explicitly // implemented property name. var interfaceMap = actualType.GetInterfaceMap(interfaceType); var interfacePropertyAccessors = GetPropertyAccessors(interfacePropertyInfo); var actualPropertyAccessors = interfacePropertyAccessors.Select(interfacePropertyAccessor => { var index = Array.IndexOf(interfaceMap.InterfaceMethods, interfacePropertyAccessor); return interfaceMap.TargetMethods[index]; }); // Binding must be done by accessor methods because interface // maps only map accessor methods and do not map properties. return actualType.GetTypeInfo().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Single(propertyInfo => { // we are looking for a property that implements all the required accessors var propertyAccessors = GetPropertyAccessors(propertyInfo); return actualPropertyAccessors.All(x => propertyAccessors.Contains(x)); }); #endif } } }