using System; using System.Collections.Generic; using System.Linq; using HeuristicLab.Persistence.Interfaces; using HeuristicLab.Persistence.Core; using System.Reflection; using HeuristicLab.Persistence.Auxiliary; using System.Text; using System.Reflection.Emit; namespace HeuristicLab.Persistence.Default.CompositeSerializers.Storable { /// /// Intended for serialization of all custom classes. Classes should have the /// [StorableClass] attribute set. The default mode is to serialize /// members with the [Storable] attribute set. Alternatively the /// storable mode can be set to AllFields, AllProperties /// or AllFieldsAndAllProperties. /// [StorableClass] public sealed class StorableSerializer : ICompositeSerializer { #region ICompositeSerializer implementation /// /// Priority 200, one of the first default composite serializers to try. /// /// public int Priority { get { return 200; } } /// /// Determines for every type whether the composite serializer is applicable. /// /// The type. /// /// true if this instance can serialize the specified type; otherwise, false. /// public bool CanSerialize(Type type) { bool markedStorable = StorableReflection.HasStorableClassAttribute(type); if (GetConstructor(type) == null) if (markedStorable) throw new Exception("[Storable] type has no default constructor and no [StorableConstructor]"); else return false; if (!StorableReflection.IsEmptyOrStorableType(type, true)) if (markedStorable) throw new Exception("[Storable] type has non emtpy, non [Storable] base classes"); else return false; return true; } /// /// Give a reason if possibly why the given type cannot be serialized by this /// ICompositeSerializer. /// /// The type. /// /// A string justifying why type cannot be serialized. /// public string JustifyRejection(Type type) { StringBuilder sb = new StringBuilder(); if (GetConstructor(type) == null) sb.Append("class has no default constructor and no [StorableConstructor]"); if (!StorableReflection.IsEmptyOrStorableType(type, true)) sb.Append("class (or one of its bases) is not empty and not marked [Storable]; "); return sb.ToString(); } /// /// Creates the meta info. /// /// The object. /// A list of storable components. public IEnumerable CreateMetaInfo(object o) { InvokeHook(HookType.BeforeSerialization, o); return new Tag[] { }; } /// /// Decompose an object into s, the tag name can be null, /// the order in which elements are generated is guaranteed to be /// the same as they will be supplied to the Populate method. /// /// An object. /// An enumerable of s. public IEnumerable Decompose(object obj) { foreach (var accessor in GetStorableAccessors(obj)) { yield return new Tag(accessor.Name, accessor.Get()); } } /// /// Create an instance of the object using the provided meta information. /// /// A type. /// The meta information. /// A fresh instance of the provided type. public object CreateInstance(Type type, IEnumerable metaInfo) { try { return GetConstructor(type)(); } catch (TargetInvocationException x) { throw new PersistenceException( "Could not instantiate storable object: Encountered exception during constructor call", x.InnerException); } } /// /// Populates the specified instance. /// /// The instance. /// The objects. /// The type. public void Populate(object instance, IEnumerable objects, Type type) { var memberDict = new Dictionary(); IEnumerator iter = objects.GetEnumerator(); while (iter.MoveNext()) { memberDict.Add(iter.Current.Name, iter.Current); } foreach (var accessor in GetStorableAccessors(instance)) { if (memberDict.ContainsKey(accessor.Name)) { accessor.Set(memberDict[accessor.Name].Value); } else if (accessor.DefaultValue != null) { accessor.Set(accessor.DefaultValue); } } InvokeHook(HookType.AfterDeserialization, instance); } #endregion #region constants & private data types private const BindingFlags ALL_CONSTRUCTORS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly object[] emptyArgs = new object[] { }; private sealed class HookDesignator { public Type Type { get; private set; } public HookType HookType { get; private set; } public HookDesignator() { } public HookDesignator(Type type, HookType hookType) { Type = type; HookType = HookType; } } private sealed class MemberCache : Dictionary> { } #endregion #region caches private MemberCache storableMemberCache = new MemberCache(); private delegate object Constructor(); private Dictionary constructorCache = new Dictionary(); private Dictionary> hookCache = new Dictionary>(); #endregion #region attribute access private IEnumerable GetStorableMembers(Type type) { lock (storableMemberCache) { if (storableMemberCache.ContainsKey(type)) return storableMemberCache[type]; var storablesMembers = StorableReflection.GenerateStorableMembers(type); storableMemberCache[type] = storablesMembers; return storablesMembers; } } private Constructor GetConstructor(Type type) { lock (constructorCache) { if (constructorCache.ContainsKey(type)) return constructorCache[type]; Constructor c = FindStorableConstructor(type) ?? GetDefaultConstructor(type); constructorCache.Add(type, c); return c; } } private Constructor GetDefaultConstructor(Type type) { ConstructorInfo ci = type.GetConstructor(ALL_CONSTRUCTORS, null, Type.EmptyTypes, null); if (ci == null) return null; DynamicMethod dm = new DynamicMethod("", typeof(object), null, type); ILGenerator ilgen = dm.GetILGenerator(); ilgen.Emit(OpCodes.Newobj, ci); ilgen.Emit(OpCodes.Ret); return (Constructor)dm.CreateDelegate(typeof(Constructor)); } private Constructor FindStorableConstructor(Type type) { foreach (ConstructorInfo ci in type.GetConstructors(ALL_CONSTRUCTORS)) { if (ci.GetCustomAttributes(typeof(StorableConstructorAttribute), false).Length > 0) { if (ci.GetParameters().Length != 1 || ci.GetParameters()[0].ParameterType != typeof(bool)) throw new PersistenceException("StorableConstructor must have exactly one argument of type bool"); DynamicMethod dm = new DynamicMethod("", typeof(object), null, type); ILGenerator ilgen = dm.GetILGenerator(); ilgen.Emit(OpCodes.Ldc_I4_1); // load true ilgen.Emit(OpCodes.Newobj, ci); ilgen.Emit(OpCodes.Ret); return (Constructor)dm.CreateDelegate(typeof(Constructor)); } } return null; } private IEnumerable GetStorableAccessors(object obj) { return GetStorableMembers(obj.GetType()) .Select(mi => new DataMemberAccessor(mi.MemberInfo, mi.DisentangledName, mi.DefaultValue, obj)); } private void InvokeHook(HookType hookType, object obj) { if (obj == null) throw new ArgumentNullException("Cannot invoke hooks on null"); foreach (StorableReflection.Hook hook in GetHooks(hookType, obj.GetType())) { hook(obj); } } private IEnumerable GetHooks(HookType hookType, Type type) { lock (hookCache) { List hooks; var designator = new HookDesignator(type, hookType); hookCache.TryGetValue(designator, out hooks); if (hooks != null) return hooks; hooks = new List(StorableReflection.CollectHooks(hookType, type)); hookCache.Add(designator, hooks); return hooks; } } #endregion } }