#region License Information
/* HeuristicLab
* Copyright (C) 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
}
}