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.
464 lines
16 KiB
464 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>The member map.</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);
|
|
}
|
|
}
|
|
|
|
// lambdaExpression = (obj) => (object) ((TClass) obj).Member
|
|
var objParameter = Expression.Parameter(typeof(object), "obj");
|
|
var lambdaExpression = Expression.Lambda<Func<object, object>>(
|
|
Expression.Convert(
|
|
Expression.MakeMemberAccess(
|
|
Expression.Convert(objParameter, memberInfo.DeclaringType),
|
|
memberInfo
|
|
),
|
|
typeof(object)
|
|
),
|
|
objParameter
|
|
);
|
|
|
|
return lambdaExpression.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);
|
|
}
|
|
|
|
// 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<Action<object, object>>(
|
|
Expression.Call(
|
|
Expression.Convert(objParameter, memberInfo.DeclaringType),
|
|
setMethodInfo,
|
|
Expression.Convert(valueParameter, memberType)
|
|
),
|
|
objParameter,
|
|
valueParameter
|
|
);
|
|
|
|
return lambdaExpression.Compile();
|
|
}
|
|
#endregion
|
|
}
|
|
}
|