/* 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.Linq.Expressions; using System.Reflection; #if !ENABLE_IL2CPP using System.Reflection.Emit; #endif using MongoDB.Bson.Serialization.Serializers; namespace MongoDB.Bson.Serialization { /// /// Represents the mapping between a field or property and a BSON element. /// public class BsonMemberMap { // private fields private readonly BsonClassMap _classMap; private readonly MemberInfo _memberInfo; private readonly Type _memberType; private readonly bool _memberTypeIsBsonValue; private string _elementName; private bool _frozen; // once a class map has been frozen no further changes are allowed private int _order; private Func _getter; private Action _setter; private volatile IBsonSerializer _serializer; private IIdGenerator _idGenerator; private bool _isRequired; private Func _shouldSerializeMethod; private bool _ignoreIfDefault; private bool _ignoreIfNull; private object _defaultValue; private Func _defaultValueCreator; private bool _defaultValueSpecified; // constructors /// /// Initializes a new instance of the BsonMemberMap class. /// /// The class map this member map belongs to. /// The member info. public BsonMemberMap(BsonClassMap classMap, MemberInfo memberInfo) { _classMap = classMap; _memberInfo = memberInfo; _memberType = BsonClassMap.GetMemberInfoType(memberInfo); _memberTypeIsBsonValue = typeof(BsonValue).GetTypeInfo().IsAssignableFrom(_memberType); Reset(); } // public properties /// /// Gets the class map that this member map belongs to. /// public BsonClassMap ClassMap { get { return _classMap; } } /// /// Gets the name of the member. /// public string MemberName { get { return _memberInfo.Name; } } /// /// Gets the type of the member. /// public Type MemberType { get { return _memberType; } } /// /// Gets whether the member type is a BsonValue. /// public bool MemberTypeIsBsonValue { get { return _memberTypeIsBsonValue; } } /// /// Gets the name of the element. /// public string ElementName { get { return _elementName; } } /// /// Gets the serialization order. /// public int Order { get { return _order; } } /// /// Gets the member info. /// public MemberInfo MemberInfo { get { return _memberInfo; } } /// /// Gets the getter function. /// public Func Getter { get { if (_getter == null) { _getter = GetGetter(); } return _getter; } } /// /// Gets the setter function. /// public Action Setter { get { if (_setter == null) { if (_memberInfo is FieldInfo) { _setter = GetFieldSetter(); } else { _setter = GetPropertySetter(); } } return _setter; } } /// /// Gets the Id generator. /// public IIdGenerator IdGenerator { get { return _idGenerator; } } /// /// Gets whether a default value was specified. /// public bool IsDefaultValueSpecified { get { return _defaultValueSpecified; } } /// /// Gets whether an element is required for this member when deserialized. /// public bool IsRequired { get { return _isRequired; } } /// /// Gets the method that will be called to determine whether the member should be serialized. /// public Func ShouldSerializeMethod { get { return _shouldSerializeMethod; } } /// /// Gets whether default values should be ignored when serialized. /// public bool IgnoreIfDefault { get { return _ignoreIfDefault; } } /// /// Gets whether null values should be ignored when serialized. /// public bool IgnoreIfNull { get { return _ignoreIfNull; } } /// /// Gets the default value. /// public object DefaultValue { get { return _defaultValueCreator != null ? _defaultValueCreator() : _defaultValue; } } /// /// Gets whether the member is readonly. /// /// /// Readonly indicates that the member is written to the database, but not read from the database. /// public bool IsReadOnly { get { if (_memberInfo is FieldInfo) { var field = (FieldInfo)_memberInfo; return field.IsInitOnly || field.IsLiteral; } else if (_memberInfo is PropertyInfo) { var property = (PropertyInfo)_memberInfo; return !property.CanWrite; } else { throw new NotSupportedException( string.Format("Only fields and properties are supported by BsonMemberMap. The member {0} of class {1} is a {2}.", _memberInfo.Name, _memberInfo.DeclaringType.Name, _memberInfo is FieldInfo ? "field" : "property")); } } } // public methods /// /// Applies the default value to the member of an object. /// /// The object. public void ApplyDefaultValue(object obj) { if (_defaultValueSpecified) { this.Setter(obj, DefaultValue); } } /// /// Freezes this instance. /// public void Freeze() { _frozen = true; } /// /// Gets the serializer. /// /// The serializer. public IBsonSerializer GetSerializer() { if (_serializer == null) { // return special serializer for BsonValue members that handles the _csharpnull representation if (_memberTypeIsBsonValue) { var wrappedSerializer = BsonSerializer.LookupSerializer(_memberType); var isBsonArraySerializer = wrappedSerializer is IBsonArraySerializer; var isBsonDocumentSerializer = wrappedSerializer is IBsonDocumentSerializer; Type csharpNullSerializerDefinition; if (isBsonArraySerializer && isBsonDocumentSerializer) { csharpNullSerializerDefinition = typeof(BsonValueCSharpNullArrayAndDocumentSerializer<>); } else if (isBsonArraySerializer) { csharpNullSerializerDefinition = typeof(BsonValueCSharpNullArraySerializer<>); } else if (isBsonDocumentSerializer) { csharpNullSerializerDefinition = typeof(BsonValueCSharpNullDocumentSerializer<>); } else { csharpNullSerializerDefinition = typeof(BsonValueCSharpNullSerializer<>); } var csharpNullSerializerType = csharpNullSerializerDefinition.MakeGenericType(_memberType); var csharpNullSerializer = (IBsonSerializer)Activator.CreateInstance(csharpNullSerializerType, wrappedSerializer); _serializer = csharpNullSerializer; } else { _serializer = BsonSerializer.LookupSerializer(_memberType); } } return _serializer; } /// /// Resets the member map back to its initial state. /// /// The member map. public BsonMemberMap Reset() { if (_frozen) { ThrowFrozenException(); } _defaultValue = GetDefaultValue(_memberType); _defaultValueCreator = null; _defaultValueSpecified = false; _elementName = _memberInfo.Name; _idGenerator = null; _ignoreIfDefault = false; _ignoreIfNull = false; _isRequired = false; _order = int.MaxValue; _serializer = null; _shouldSerializeMethod = null; return this; } /// /// Sets the default value creator. /// /// The default value creator (note: the supplied delegate must be thread safe). /// The member map. public BsonMemberMap SetDefaultValue(Func defaultValueCreator) { if (defaultValueCreator == null) { throw new ArgumentNullException("defaultValueCreator"); } if (_frozen) { ThrowFrozenException(); } _defaultValue = defaultValueCreator(); // need an instance to compare against _defaultValueCreator = defaultValueCreator; _defaultValueSpecified = true; return this; } /// /// Sets the default value. /// /// The default value. /// The member map. public BsonMemberMap SetDefaultValue(object defaultValue) { if (_frozen) { ThrowFrozenException(); } _defaultValue = defaultValue; _defaultValueCreator = null; _defaultValueSpecified = true; return this; } /// /// Sets the name of the element. /// /// The name of the element. /// The member map. public BsonMemberMap SetElementName(string elementName) { if (elementName == null) { throw new ArgumentNullException("elementName"); } if (elementName.IndexOf('\0') != -1) { throw new ArgumentException("Element names cannot contain nulls.", "elementName"); } if (_frozen) { ThrowFrozenException(); } _elementName = elementName; return this; } /// /// Sets the Id generator. /// /// The Id generator. /// The member map. public BsonMemberMap SetIdGenerator(IIdGenerator idGenerator) { if (_frozen) { ThrowFrozenException(); } _idGenerator = idGenerator; return this; } /// /// Sets whether default values should be ignored when serialized. /// /// Whether default values should be ignored when serialized. /// The member map. public BsonMemberMap SetIgnoreIfDefault(bool ignoreIfDefault) { if (_frozen) { ThrowFrozenException(); } if (ignoreIfDefault && _ignoreIfNull) { throw new InvalidOperationException("IgnoreIfDefault and IgnoreIfNull are mutually exclusive. Choose one or the other."); } _ignoreIfDefault = ignoreIfDefault; return this; } /// /// Sets whether null values should be ignored when serialized. /// /// Wether null values should be ignored when serialized. /// The member map. public BsonMemberMap SetIgnoreIfNull(bool ignoreIfNull) { if (_frozen) { ThrowFrozenException(); } if (ignoreIfNull && _ignoreIfDefault) { throw new InvalidOperationException("IgnoreIfDefault and IgnoreIfNull are mutually exclusive. Choose one or the other."); } _ignoreIfNull = ignoreIfNull; return this; } /// /// Sets whether an element is required for this member when deserialized /// /// Whether an element is required for this member when deserialized /// The member map. public BsonMemberMap SetIsRequired(bool isRequired) { if (_frozen) { ThrowFrozenException(); } _isRequired = isRequired; return this; } /// /// Sets the serialization order. /// /// The serialization order. /// The member map. public BsonMemberMap SetOrder(int order) { if (_frozen) { ThrowFrozenException(); } _order = order; return this; } /// /// Sets the serializer. /// /// The serializer. /// /// The member map. /// /// serializer /// serializer public BsonMemberMap SetSerializer(IBsonSerializer serializer) { if (serializer == null) { throw new ArgumentNullException("serializer"); } if (serializer.ValueType != _memberType) { var message = string.Format("Value type of serializer is {0} and does not match member type {1}.", serializer.ValueType.FullName, _memberType.FullName); throw new ArgumentException(message, "serializer"); } if (_frozen) { ThrowFrozenException(); } _serializer = serializer; return this; } /// /// Sets the method that will be called to determine whether the member should be serialized. /// /// The method. /// The member map. public BsonMemberMap SetShouldSerializeMethod(Func shouldSerializeMethod) { if (_frozen) { ThrowFrozenException(); } _shouldSerializeMethod = shouldSerializeMethod; return this; } /// /// Determines whether a value should be serialized /// /// The object. /// The value. /// True if the value should be serialized. public bool ShouldSerialize(object obj, object value) { if (_ignoreIfNull) { if (value == null) { return false; // don't serialize null } } if (_ignoreIfDefault) { if (object.Equals(_defaultValue, value)) { return false; // don't serialize default value } } if (_shouldSerializeMethod != null && !_shouldSerializeMethod(obj)) { // the _shouldSerializeMethod determined that the member shouldn't be serialized return false; } return true; } // private methods private static object GetDefaultValue(Type type) { var typeInfo = type.GetTypeInfo(); if (typeInfo.IsEnum) { return Enum.ToObject(type, 0); } switch (Type.GetTypeCode(type)) { case TypeCode.Empty: #if NET45 case TypeCode.DBNull: #endif case TypeCode.String: break; case TypeCode.Object: if (typeInfo.IsValueType) { return Activator.CreateInstance(type); } break; case TypeCode.Boolean: return false; case TypeCode.Char: return '\0'; case TypeCode.SByte: return (sbyte)0; case TypeCode.Byte: return (byte)0; case TypeCode.Int16: return (short)0; case TypeCode.UInt16: return (ushort)0; case TypeCode.Int32: return 0; case TypeCode.UInt32: return 0U; case TypeCode.Int64: return 0L; case TypeCode.UInt64: return 0UL; case TypeCode.Single: return 0F; case TypeCode.Double: return 0D; case TypeCode.Decimal: return 0M; case TypeCode.DateTime: return DateTime.MinValue; } return null; } private Action GetFieldSetter() { var fieldInfo = (FieldInfo)_memberInfo; if (IsReadOnly) { var message = string.Format( "The field '{0} {1}' of class '{2}' is readonly. To avoid this exception, call IsReadOnly to ensure that setting a value is allowed.", fieldInfo.FieldType.FullName, fieldInfo.Name, fieldInfo.DeclaringType.FullName); throw new BsonSerializationException(message); } #if ENABLE_IL2CPP return (obj, value) => { fieldInfo.SetValue(obj, value); }; #else var sourceType = fieldInfo.DeclaringType; var method = new DynamicMethod("Set" + fieldInfo.Name, null, new[] { typeof(object), typeof(object) }, true); var gen = method.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Castclass, sourceType); gen.Emit(OpCodes.Ldarg_1); gen.Emit(OpCodes.Unbox_Any, fieldInfo.FieldType); gen.Emit(OpCodes.Stfld, fieldInfo); gen.Emit(OpCodes.Ret); return (Action)method.CreateDelegate(typeof(Action)); #endif } private Func GetGetter() { #if ENABLE_IL2CPP PropertyInfo propertyInfo = _memberInfo as PropertyInfo; if (propertyInfo != null) { MethodInfo getMethodInfo = propertyInfo.GetGetMethod(); if (getMethodInfo == null) { var message = string.Format( "The property '{0} {1}' of class '{2}' has no 'get' accessor.", propertyInfo.PropertyType.FullName, propertyInfo.Name, propertyInfo.DeclaringType.FullName); throw new BsonSerializationException(message); } return (obj) => { return getMethodInfo.Invoke(obj, null); }; } FieldInfo fieldInfo = _memberInfo as FieldInfo; return (obj) => { return fieldInfo.GetValue(obj); }; #else var propertyInfo = _memberInfo as PropertyInfo; if (propertyInfo != null) { var getMethodInfo = propertyInfo.GetMethod; if (getMethodInfo == null) { var message = string.Format( "The property '{0} {1}' of class '{2}' has no 'get' accessor.", propertyInfo.PropertyType.FullName, propertyInfo.Name, propertyInfo.DeclaringType.FullName); throw new BsonSerializationException(message); } } // lambdaExpression = (obj) => (object) ((TClass) obj).Member var objParameter = Expression.Parameter(typeof(object), "obj"); var lambdaExpression = Expression.Lambda>( Expression.Convert( Expression.MakeMemberAccess( Expression.Convert(objParameter, _memberInfo.DeclaringType), _memberInfo ), typeof(object) ), objParameter ); return lambdaExpression.Compile(); #endif } private Action GetPropertySetter() { #if ENABLE_IL2CPP var propertyInfo = (PropertyInfo) _memberInfo; return (obj, value) => { propertyInfo.SetValue(obj, value); }; #else var propertyInfo = (PropertyInfo)_memberInfo; var setMethodInfo = propertyInfo.SetMethod; if (IsReadOnly) { var message = string.Format( "The property '{0} {1}' of class '{2}' has no 'set' accessor. To avoid this exception, call IsReadOnly to ensure that setting a value is allowed.", propertyInfo.PropertyType.FullName, propertyInfo.Name, propertyInfo.DeclaringType.FullName); throw new BsonSerializationException(message); } // lambdaExpression = (obj, value) => ((TClass) obj).SetMethod((TMember) value) var objParameter = Expression.Parameter(typeof(object), "obj"); var valueParameter = Expression.Parameter(typeof(object), "value"); var lambdaExpression = Expression.Lambda>( Expression.Call( Expression.Convert(objParameter, _memberInfo.DeclaringType), setMethodInfo, Expression.Convert(valueParameter, _memberType) ), objParameter, valueParameter ); return lambdaExpression.Compile(); #endif } private void ThrowFrozenException() { var message = string.Format("Member map for {0}.{1} has been frozen and no further changes are allowed.", _classMap.ClassType.FullName, _memberInfo.Name); throw new InvalidOperationException(message); } } }