You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

462 lines
16 KiB

/* 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 {
/// <summary>
/// Represents the mapping between a field or property and a BSON element.
/// </summary>
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<object, object> getter;
private Action<object, object> setter;
private IBsonSerializationOptions serializationOptions;
private IBsonSerializer serializer;
private IIdGenerator idGenerator;
private bool isRequired;
private bool hasDefaultValue;
private bool serializeDefaultValue = true;
private Func<object, bool> shouldSerializeMethod = (obj) => true;
private bool ignoreIfNull;
private object defaultValue;
#endregion
#region constructors
/// <summary>
/// Initializes a new instance of the BsonMemberMap class.
/// </summary>
/// <param name="memberInfo">The member info.</param>
/// <param name="conventions">The conventions to use with this member.</param>
public BsonMemberMap(
MemberInfo memberInfo,
ConventionProfile conventions
) {
this.memberInfo = memberInfo;
this.memberType = BsonClassMap.GetMemberInfoType(memberInfo);
this.conventions = conventions;
}
#endregion
#region public properties
/// <summary>
/// Gets the name of the member.
/// </summary>
public string MemberName {
get { return memberInfo.Name; }
}
/// <summary>
/// Gets the type of the member.
/// </summary>
public Type MemberType {
get { return memberType; }
}
/// <summary>
/// Gets the name of the element.
/// </summary>
public string ElementName {
get {
if (elementName == null) {
elementName = conventions.ElementNameConvention.GetElementName(memberInfo);
}
return elementName;
}
}
/// <summary>
/// Gets the serialization order.
/// </summary>
public int Order {
get { return order; }
}
/// <summary>
/// Gets the member info.
/// </summary>
public MemberInfo MemberInfo {
get { return memberInfo; }
}
/// <summary>
/// Gets the getter function.
/// </summary>
public Func<object, object> Getter {
get {
if (getter == null) {
getter = GetGetter();
}
return getter;
}
}
/// <summary>
/// Gets the serialization options.
/// </summary>
public IBsonSerializationOptions SerializationOptions {
get { return serializationOptions; }
}
/// <summary>
/// Gets the setter function.
/// </summary>
public Action<object, object> Setter {
get {
if (setter == null) {
if (memberInfo.MemberType == MemberTypes.Field) {
setter = GetFieldSetter();
} else {
setter = GetPropertySetter();
}
}
return setter;
}
}
/// <summary>
/// Gets the Id generator.
/// </summary>
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;
}
}
/// <summary>
/// Gets whether an element is required for this member when deserialized.
/// </summary>
public bool IsRequired {
get { return isRequired; }
}
/// <summary>
/// Gets whether this member has a default value.
/// </summary>
public bool HasDefaultValue {
get { return hasDefaultValue; }
}
/// <summary>
/// Gets whether the default value should be serialized.
/// </summary>
public bool SerializeDefaultValue {
get { return serializeDefaultValue; }
}
/// <summary>
/// Gets the method that will be called to determine whether the member should be serialized.
/// </summary>
public Func<object, bool> ShouldSerializeMethod {
get { return shouldSerializeMethod; }
}
/// <summary>
/// Gets whether null values should be ignored when serialized.
/// </summary>
public bool IgnoreIfNull {
get { return ignoreIfNull; }
}
/// <summary>
/// Gets the default value.
/// </summary>
public object DefaultValue {
get { return defaultValue; }
}
#endregion
#region public methods
/// <summary>
/// Applies the default value to the member of an object.
/// </summary>
/// <param name="obj">The object.</param>
public void ApplyDefaultValue(
object obj
) {
if (!hasDefaultValue) {
throw new InvalidOperationException("BsonMemberMap has no default value.");
}
this.Setter(obj, defaultValue);
}
/// <summary>
/// Gets the serializer.
/// </summary>
/// <param name="actualType">The actual type of the member's value.</param>
/// <returns>The member map.</returns>
public IBsonSerializer GetSerializer(
Type actualType
) {
if (serializer != null) {
return serializer;
} else {
return BsonSerializer.LookupSerializer(actualType);
}
}
/// <summary>
/// Sets the default value.
/// </summary>
/// <param name="defaultValue">The default value.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetDefaultValue(
object defaultValue
) {
this.defaultValue = defaultValue;
this.hasDefaultValue = true;
return this;
}
/// <summary>
/// Sets the default value.
/// </summary>
/// <param name="defaultValue">The default value.</param>
/// <param name="serializeDefaultValue">Whether the default value shoudl be serialized.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetDefaultValue(
object defaultValue,
bool serializeDefaultValue
) {
SetDefaultValue(defaultValue);
SetSerializeDefaultValue(serializeDefaultValue);
return this;
}
/// <summary>
/// Sets the name of the element.
/// </summary>
/// <param name="elementName">The name of the element.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetElementName(
string elementName
) {
this.elementName = elementName;
return this;
}
/// <summary>
/// Sets the Id generator.
/// </summary>
/// <param name="idGenerator">The Id generator.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetIdGenerator(
IIdGenerator idGenerator
) {
this.idGenerator = idGenerator;
return this;
}
/// <summary>
/// Sets whether null values should be ignored when serialized.
/// </summary>
/// <param name="ignoreIfNull">Wether null values should be ignored when serialized.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetIgnoreIfNull(
bool ignoreIfNull
) {
this.ignoreIfNull = ignoreIfNull;
return this;
}
/// <summary>
/// Sets whether an element is required for this member when deserialized
/// </summary>
/// <param name="isRequired">Whether an element is required for this member when deserialized</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetIsRequired(
bool isRequired
) {
this.isRequired = isRequired;
return this;
}
/// <summary>
/// Sets the serialization order.
/// </summary>
/// <param name="order">The serialization order.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetOrder(
int order
) {
this.order = order;
return this;
}
/// <summary>
/// Sets the external representation.
/// </summary>
/// <param name="representation">The external representation.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetRepresentation(
BsonType representation
) {
this.serializationOptions = new RepresentationSerializationOptions(representation);
return this;
}
/// <summary>
/// Sets the serialization options.
/// </summary>
/// <param name="serializationOptions">The serialization options.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetSerializationOptions(
IBsonSerializationOptions serializationOptions
) {
this.serializationOptions = serializationOptions;
return this;
}
/// <summary>
/// Sets the serializer.
/// </summary>
/// <param name="serializer">The serializer.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetSerializer(
IBsonSerializer serializer
) {
this.serializer = serializer;
return this;
}
/// <summary>
/// Sets whether the default value should be serialized.
/// </summary>
/// <param name="serializeDefaultValue">Whether the default value should be serialized.</param>
/// <returns>The member map.</returns>
public BsonMemberMap SetSerializeDefaultValue(
bool serializeDefaultValue
) {
this.serializeDefaultValue = serializeDefaultValue;
return this;
}
/// <summary>
/// Sets the method that will be called to determine whether the member should be serialized.
/// </summary>
/// <param name="shouldSerializeMethod">The method.</param>
/// <returns></returns>
public BsonMemberMap SetShouldSerializeMethod(
Func<object, bool> shouldSerializeMethod
) {
if (shouldSerializeMethod != null) {
this.shouldSerializeMethod = shouldSerializeMethod;
} else {
this.shouldSerializeMethod = (obj) => true;
}
return this;
}
#endregion
#region private methods
private Action<object, object> 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<object, object>) method.CreateDelegate(typeof(Action<object, object>));
}
private Func<object, object> 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);
}
}
var instance = Expression.Parameter(typeof(object), "obj");
var lambda = Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.MakeMemberAccess(
Expression.Convert(instance, memberInfo.DeclaringType),
memberInfo
),
typeof(object)
),
instance
);
return lambda.Compile();
}
private Action<object, object> 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);
}
var instance = Expression.Parameter(typeof(object), "obj");
var argument = Expression.Parameter(typeof(object), "a");
var lambda = Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(instance, memberInfo.DeclaringType),
setMethodInfo,
Expression.Convert(argument, memberType)
),
instance,
argument
);
return lambda.Compile();
}
#endregion
}
}