/* 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);
}
}
}