/* Copyright 2010-2011 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.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Text; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Conventions; using MongoDB.Bson.Serialization.IdGenerators; using MongoDB.Bson.Serialization.Options; namespace MongoDB.Bson.Serialization { /// /// Represents the mapping between a field or property and a BSON element. /// public class BsonMemberMap { #region private fields private ConventionProfile conventions; private string elementName; private int order = int.MaxValue; private MemberInfo memberInfo; private Type memberType; private Func getter; private Action setter; private IBsonSerializationOptions serializationOptions; private IBsonSerializer serializer; private IIdGenerator idGenerator; private bool isRequired; private bool hasDefaultValue; private bool serializeDefaultValue = true; private Func shouldSerializeMethod = (obj) => true; private bool ignoreIfNull; private object defaultValue; #endregion #region constructors /// /// Initializes a new instance of the BsonMemberMap class. /// /// The member info. /// The conventions to use with this member. public BsonMemberMap( MemberInfo memberInfo, ConventionProfile conventions ) { this.memberInfo = memberInfo; this.memberType = BsonClassMap.GetMemberInfoType(memberInfo); this.conventions = conventions; } #endregion #region public properties /// /// 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 the name of the element. /// public string ElementName { get { if (elementName == null) { elementName = conventions.ElementNameConvention.GetElementName(memberInfo); } 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 { if (idGenerator == null) { // special case IdGenerator for strings represented externally as ObjectId var memberInfoType = BsonClassMap.GetMemberInfoType(memberInfo); var representationOptions = serializationOptions as RepresentationSerializationOptions; if (memberInfoType == typeof(string) && representationOptions != null && representationOptions.Representation == BsonType.ObjectId) { idGenerator = StringObjectIdGenerator.Instance; } else { idGenerator = conventions.IdGeneratorConvention.GetIdGenerator(memberInfo); } } return idGenerator; } } /// /// Gets whether an element is required for this member when deserialized. /// public bool IsRequired { get { return isRequired; } } /// /// Gets whether this member has a default value. /// public bool HasDefaultValue { get { return hasDefaultValue; } } /// /// Gets whether the default value should be serialized. /// public bool SerializeDefaultValue { get { return serializeDefaultValue; } } /// /// Gets the method that will be called to determine whether the member should be serialized. /// public Func ShouldSerializeMethod { get { return shouldSerializeMethod; } } /// /// Gets whether null values should be ignored when serialized. /// public bool IgnoreIfNull { get { return ignoreIfNull; } } /// /// Gets the default value. /// public object DefaultValue { get { return defaultValue; } } #endregion #region public methods /// /// Applies the default value to the member of an object. /// /// The object. public void ApplyDefaultValue( object obj ) { if (!hasDefaultValue) { throw new InvalidOperationException("BsonMemberMap has no default value."); } this.Setter(obj, defaultValue); } /// /// Gets the serializer. /// /// The actual type of the member's value. /// The member map. public IBsonSerializer GetSerializer( Type actualType ) { if (serializer != null) { return serializer; } else { return BsonSerializer.LookupSerializer(actualType); } } /// /// Sets the default value. /// /// The default value. /// The member map. public BsonMemberMap SetDefaultValue( object defaultValue ) { this.defaultValue = defaultValue; this.hasDefaultValue = true; return this; } /// /// Sets the default value. /// /// The default value. /// Whether the default value shoudl be serialized. /// The member map. public BsonMemberMap SetDefaultValue( object defaultValue, bool serializeDefaultValue ) { SetDefaultValue(defaultValue); SetSerializeDefaultValue(serializeDefaultValue); return this; } /// /// Sets the name of the element. /// /// The name of the element. /// The member map. public BsonMemberMap SetElementName( string elementName ) { this.elementName = elementName; return this; } /// /// Sets the Id generator. /// /// The Id generator. /// The member map. public BsonMemberMap SetIdGenerator( IIdGenerator idGenerator ) { this.idGenerator = idGenerator; 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 ) { this.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 ) { this.isRequired = isRequired; return this; } /// /// Sets the serialization order. /// /// The serialization order. /// The member map. public BsonMemberMap SetOrder( int order ) { this.order = order; return this; } /// /// Sets the external representation. /// /// The external representation. /// The member map. public BsonMemberMap SetRepresentation( BsonType representation ) { this.serializationOptions = new RepresentationSerializationOptions(representation); return this; } /// /// Sets the serialization options. /// /// The serialization options. /// The member map. public BsonMemberMap SetSerializationOptions( IBsonSerializationOptions serializationOptions ) { this.serializationOptions = serializationOptions; return this; } /// /// Sets the serializer. /// /// The serializer. /// The member map. public BsonMemberMap SetSerializer( IBsonSerializer serializer ) { this.serializer = serializer; return this; } /// /// Sets whether the default value should be serialized. /// /// Whether the default value should be serialized. /// The member map. public BsonMemberMap SetSerializeDefaultValue( bool serializeDefaultValue ) { this.serializeDefaultValue = serializeDefaultValue; 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 (shouldSerializeMethod != null) { this.shouldSerializeMethod = shouldSerializeMethod; } else { this.shouldSerializeMethod = (obj) => true; } return this; } #endregion #region private methods private Action GetFieldSetter() { var fieldInfo = (FieldInfo) memberInfo; if (fieldInfo.IsInitOnly || fieldInfo.IsLiteral) { var message = string.Format("The field '{0} {1}' of class '{2}' is readonly.", 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() { if (memberInfo is PropertyInfo) { var propertyInfo = (PropertyInfo) memberInfo; 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 (setMethodInfo == null) { var message = string.Format("The property '{0} {1}' of class '{2}' has no 'set' accessor.", 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(); } #endregion } }