/* Copyright 2010-2014 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;
using System.Reflection.Emit;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Bson.Serialization.Options;
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 IBsonSerializationOptions _serializationOptions;
private IBsonSerializer _serializer;
private volatile IDiscriminatorConvention _cachedDiscriminatorConvention;
private volatile IBsonSerializer _cachedSerializer;
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).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 serialization options.
///
public IBsonSerializationOptions SerializationOptions
{
get { return _serializationOptions; }
}
///
/// Gets the setter function.
///
public Action Setter
{
get
{
if (_setter == null)
{
if (_memberInfo.MemberType == MemberTypes.Field)
{
_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
{
switch(_memberInfo.MemberType)
{
case MemberTypes.Field:
var field = (FieldInfo)_memberInfo;
return field.IsInitOnly || field.IsLiteral;
case MemberTypes.Property:
var property = (PropertyInfo)_memberInfo;
return !property.CanWrite;
default:
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.MemberType));
}
}
}
// 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 actual type of the member's value.
/// The member map.
public IBsonSerializer GetSerializer(Type actualType)
{
// if a custom serializer is configured always return it
if (_serializer != null)
{
return _serializer;
}
else
{
// return special serializer for BsonValue members that handles the _csharpnull representation
if (_memberTypeIsBsonValue)
{
return BsonValueCSharpNullSerializer.Instance;
}
// return a cached serializer when possible
if (actualType == _memberType)
{
var serializer = _cachedSerializer;
if (serializer == null)
{
// it's possible but harmless for multiple threads to do the initial lookup at the same time
serializer = BsonSerializer.LookupSerializer(_memberType);
_cachedSerializer = serializer;
}
return serializer;
}
else
{
return BsonSerializer.LookupSerializer(actualType);
}
}
}
///
/// 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;
_serializationOptions = null;
_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 external representation.
///
/// The external representation.
/// The member map.
public BsonMemberMap SetRepresentation(BsonType representation)
{
if (_frozen) { ThrowFrozenException(); }
_serializationOptions = new RepresentationSerializationOptions(representation);
return this;
}
///
/// Sets the serialization options.
///
/// The serialization options.
/// The member map.
public BsonMemberMap SetSerializationOptions(IBsonSerializationOptions serializationOptions)
{
if (_frozen) { ThrowFrozenException(); }
_serializationOptions = serializationOptions;
return this;
}
///
/// Sets the serializer.
///
/// The serializer.
/// The member map.
public BsonMemberMap SetSerializer(IBsonSerializer 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;
}
// internal methods
///
/// Gets the discriminator convention for the member type.
///
/// The discriminator convention for the member type.
internal IDiscriminatorConvention GetDiscriminatorConvention()
{
// return a cached discriminator convention when possible
var discriminatorConvention = _cachedDiscriminatorConvention;
if (discriminatorConvention == null)
{
// it's possible but harmless for multiple threads to do the initial lookup at the same time
discriminatorConvention = BsonSerializer.LookupDiscriminatorConvention(_memberType);
_cachedDiscriminatorConvention = discriminatorConvention;
}
return discriminatorConvention;
}
// private methods
private static object GetDefaultValue(Type type)
{
if (type.IsEnum)
{
return Enum.ToObject(type, 0);
}
switch (Type.GetTypeCode(type))
{
case TypeCode.Empty:
case TypeCode.DBNull:
case TypeCode.String:
break;
case TypeCode.Object:
if (type.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);
}
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));
}
private Func GetGetter()
{
var propertyInfo = _memberInfo as PropertyInfo;
if (propertyInfo != null)
{
var getMethodInfo = propertyInfo.GetGetMethod(true);
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();
}
private Action GetPropertySetter()
{
var propertyInfo = (PropertyInfo)_memberInfo;
var setMethodInfo = propertyInfo.GetSetMethod(true);
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();
}
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);
}
}
}