#region License Information /* HeuristicLab * Copyright (C) 2002-2019 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Text; using HEAL.Attic; using HeuristicLab.Persistence.Core; using HeuristicLab.Persistence.Interfaces; namespace HeuristicLab.Persistence.Default.CompositeSerializers.Storable { /// /// Intended for serialization of all custom classes. Classes should have the /// [StorableType] 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. /// [StorableType("F60343E8-4337-4171-A50A-6A57D09267ED")] public sealed class StorableSerializer : ICompositeSerializer { public StorableSerializer() { accessorListCache = new AccessorListCache(); accessorCache = new AccessorCache(); constructorCache = new Dictionary(); hookCache = new Dictionary>(); } [StorableConstructor] private StorableSerializer(StorableConstructorFlag _) { } #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) { if (type.IsEnum || type.IsValueType) return false; // there are other more specific serializers for enums and structs var markedStorable = StorableReflection.HasStorableTypeAttribute(type); if (GetConstructor(type) == null) if (markedStorable && !type.IsInterface) throw new Exception("[Storable] type has no default constructor and no [StorableConstructor]"); else return false; if (!StorableReflection.IsEmptyOrStorableType(type, true)) if (markedStorable && !type.IsInterface) 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) { var 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) { return from accessor in GetStorableAccessors(obj.GetType()) where accessor.Get != null select new Tag(accessor.Name, accessor.Get(obj)); } /// /// 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(); var iter = objects.GetEnumerator(); while (iter.MoveNext()) { memberDict.Add(iter.Current.Name, iter.Current); } foreach (var accessor in GetStorableAccessors(instance.GetType())) { if (accessor.Set != null) { if (memberDict.ContainsKey(accessor.Name)) { accessor.Set(instance, memberDict[accessor.Name].Value); } else if (accessor.DefaultValue != null) { accessor.Set(instance, accessor.DefaultValue); } } } InvokeHook(HookType.AfterDeserialization, instance); } #endregion #region constants & private data types private const BindingFlags ALL_CONSTRUCTORS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private sealed class HookDesignator : Tuple { public HookDesignator(Type type, HookType hookType) : base(type, hookType) { } } private sealed class AccessorListCache : Dictionary> { } private sealed class AccessorCache : Dictionary { } private delegate object Constructor(); #endregion #region caches private readonly AccessorListCache accessorListCache; private readonly AccessorCache accessorCache; private readonly Dictionary constructorCache; private readonly Dictionary> hookCache; #endregion #region attribute access private IEnumerable GetStorableAccessors(Type type) { lock (accessorListCache) { if (accessorListCache.ContainsKey(type)) return accessorListCache[type]; var storableMembers = StorableReflection .GenerateStorableMembers(type) .Select(GetMemberAccessor) .ToList(); accessorListCache[type] = storableMembers; return storableMembers; } } private DataMemberAccessor GetMemberAccessor(StorableMemberInfo mi) { lock (accessorCache) { if (accessorCache.ContainsKey(mi.MemberInfo)) return new DataMemberAccessor(accessorCache[mi.MemberInfo], mi.DisentangledName, mi.DefaultValue); var dma = new DataMemberAccessor(mi.MemberInfo, mi.DisentangledName, mi.DefaultValue); accessorCache[mi.MemberInfo] = dma; return dma; } } private Constructor GetConstructor(Type type) { lock (constructorCache) { if (constructorCache.ContainsKey(type)) return constructorCache[type]; var c = FindStorableConstructor(type) ?? GetDefaultConstructor(type); constructorCache.Add(type, c); return c; } } private Constructor GetDefaultConstructor(Type type) { var ci = type.GetConstructor(ALL_CONSTRUCTORS, null, Type.EmptyTypes, null); if (ci == null) return null; var dm = new DynamicMethod("", typeof(object), null, type, true); var ilgen = dm.GetILGenerator(); ilgen.Emit(OpCodes.Newobj, ci); ilgen.Emit(OpCodes.Ret); return (Constructor)dm.CreateDelegate(typeof(Constructor)); } private Constructor FindStorableConstructor(Type type) { foreach (var ci in type .GetConstructors(ALL_CONSTRUCTORS) .Where(ci => ci.GetCustomAttributes(typeof(StorableConstructorAttribute), false).Length > 0)) { if (ci.GetParameters().Length != 1 || ci.GetParameters()[0].ParameterType != typeof(StorableConstructorFlag)) throw new PersistenceException("StorableConstructor must have exactly one argument of type StorableConstructorFlag"); var dm = new DynamicMethod("", typeof(object), null, type, true); var ilgen = dm.GetILGenerator(); var defaultFlagFieldInfo = typeof(StorableConstructorFlag).GetField("Default", BindingFlags.Static | BindingFlags.Public); ilgen.Emit(OpCodes.Ldsfld, defaultFlagFieldInfo); // load the object ilgen.Emit(OpCodes.Newobj, ci); ilgen.Emit(OpCodes.Ret); return (Constructor)dm.CreateDelegate(typeof(Constructor)); } return null; } private void InvokeHook(HookType hookType, object obj) { if (obj == null) throw new ArgumentNullException("obj"); foreach (var 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 } }