using System.Collections.Generic; using System.Collections; using System; using System.Linq; using HeuristicLab.Persistence.Auxiliary; using HeuristicLab.Persistence.Interfaces; using HeuristicLab.Persistence.Core.Tokens; using HeuristicLab.Persistence.Default.CompositeSerializers.Storable; using System.Text; using System.Reflection; using System.IO; using System.Diagnostics; namespace HeuristicLab.Persistence.Core { /// /// The core hub for serialization. This class transforms an object graph /// into a tree and later into a stream of serialization tokens using /// the given configuration. /// /// Primitive serializers directly format an object to a serializable type. /// /// Composite serializers decompose an object into other object that are then /// recursively serialized. /// /// A constructed serializer is enumerable and continuously analyses /// and traverses the object graph while the enumerator is iterated /// public class Serializer : IEnumerable { private class ReferenceEqualityComparer : IEqualityComparer { public new bool Equals(object a, object b) { return Object.ReferenceEquals(a, b); } public int GetHashCode(object obj) { if (obj == null) return 0; return obj.GetHashCode(); } } private readonly object obj; private readonly string rootName; private readonly Dictionary obj2id; private readonly Dictionary typeCache; private readonly Configuration configuration; private readonly bool isTestRun; private readonly List exceptions; /// /// Gets or sets a value indicating whether to interleave type information /// while serializing an object. /// /// Alternatively the type information can be obtained through the /// Property after serialization is done. /// /// /// true if type information should be interleaved; otherwise, false. /// public bool InterleaveTypeInformation { get; set; } /// /// Contains a mapping of type id to type and serializer. /// /// The type cache. public List TypeCache { get { BuildTypeCache(); return externalTypeCache; } } /// /// Contains a list of files (mostly assemblies) that are /// necessary to deserialize the object graph again. /// public List RequiredFiles { get { BuildTypeCache(); return requiredFiles; } } private List externalTypeCache; private List requiredFiles; private void BuildTypeCache() { externalTypeCache = new List(); Dictionary assemblies = new Dictionary(); foreach (var pair in typeCache) { string serializer = null; IPrimitiveSerializer f = configuration.GetPrimitiveSerializer(pair.Key); if (f != null) { serializer = f.GetType().AssemblyQualifiedName; assemblies[f.GetType().Assembly] = true; } else { ICompositeSerializer d = configuration.GetCompositeSerializer(pair.Key); serializer = d.GetType().AssemblyQualifiedName; assemblies[d.GetType().Assembly] = true; } externalTypeCache.Add(new TypeMapping(pair.Value, pair.Key.AssemblyQualifiedName, serializer)); assemblies[pair.Key.Assembly] = true; } Dictionary files = new Dictionary(); foreach (Assembly a in assemblies.Keys) { files[a.CodeBase] = true; } requiredFiles = new List(files.Keys); } /// /// Initializes a new instance of the class. /// /// The object to serialize. /// The configuration. public Serializer(object obj, Configuration configuration) : this(obj, configuration, "ROOT") { } /// /// Initializes a new instance of the class. /// /// The object to serialize. /// The configuration. /// Name of the root token. public Serializer(object obj, Configuration configuration, string rootName) : this(obj, configuration, rootName, false) { } /// /// Initializes a new instance of the class. /// /// The object to serialize. /// The configuration. /// Name of the root token. /// Try to complete the whole object graph, /// don't stop at the first exception public Serializer(object obj, Configuration configuration, string rootName, bool isTestRun) { this.InterleaveTypeInformation = false; this.obj = obj; this.rootName = rootName; this.configuration = configuration; obj2id = new Dictionary(new ReferenceEqualityComparer()) { { new object(), 0 } }; typeCache = new Dictionary(); this.isTestRun = isTestRun; this.exceptions = new List(); } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to /// iterate through the collection. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// /// Returns an enumerator that iterates through the serialization tokens. /// /// /// A that can be used to /// iterate through serialization tokens. /// public IEnumerator GetEnumerator() { var enumerator = Serialize(new DataMemberAccessor(rootName, null, () => obj, null)); if (isTestRun) { return AddExceptionCompiler(enumerator); } else { return enumerator; } } private IEnumerator AddExceptionCompiler(IEnumerator enumerator) { while (enumerator.MoveNext()) yield return enumerator.Current; if (exceptions.Count == 1) throw exceptions[0]; if (exceptions.Count > 1) throw new PersistenceException("Multiple exceptions during serialization", exceptions); } private Stack objectGraphTrace = new Stack(); private IEnumerator Serialize(DataMemberAccessor accessor) { object value = accessor.Get(); if (value == null) return NullReferenceEnumerator(accessor.Name); Type type = value.GetType(); if (obj2id.ContainsKey(value)) return ReferenceEnumerator(accessor.Name, obj2id[value]); bool emitTypeInfo = false; if (!typeCache.ContainsKey(type)) { typeCache.Add(type, typeCache.Count); emitTypeInfo = InterleaveTypeInformation; } int typeId = typeCache[type]; int? id = null; if (!type.IsValueType) { id = obj2id.Count; obj2id.Add(value, (int)id); } try { objectGraphTrace.Push(accessor.Name); IPrimitiveSerializer primitiveSerializer = configuration.GetPrimitiveSerializer(type); if (primitiveSerializer != null) return PrimitiveEnumerator( accessor.Name, typeId, primitiveSerializer.Format(value), id, emitTypeInfo); ICompositeSerializer compositeSerializer = configuration.GetCompositeSerializer(type); if (compositeSerializer != null) return CompositeEnumerator( accessor.Name, compositeSerializer.Decompose(value), id, typeId, compositeSerializer.CreateMetaInfo(value), emitTypeInfo); throw CreatePersistenceException(type); } catch (Exception x) { if (isTestRun) { exceptions.Add(x); return new List().GetEnumerator(); } else { throw; } } finally { objectGraphTrace.Pop(); } } private PersistenceException CreatePersistenceException(Type type) { StringBuilder sb = new StringBuilder(); sb.Append("Could not determine how to serialize a value of type \"") .Append(type.VersionInvariantName()) .AppendLine("\"") .Append("object graph location: ") .AppendLine(string.Join(".", objectGraphTrace.ToArray())) .AppendLine("No registered primitive serializer for this type:"); foreach (var ps in configuration.PrimitiveSerializers) sb.Append(ps.SourceType.VersionInvariantName()) .Append(" ---- (") .Append(ps.GetType().VersionInvariantName()) .AppendLine(")"); sb.AppendLine("Rejected by all composite serializers:"); foreach (var cs in configuration.CompositeSerializers) sb.Append("\"") .Append(cs.JustifyRejection(type)) .Append("\" ---- (") .Append(cs.GetType().VersionInvariantName()) .AppendLine(")"); return new PersistenceException(sb.ToString()); } private IEnumerator NullReferenceEnumerator(string name) { yield return new NullReferenceToken(name); } private IEnumerator ReferenceEnumerator(string name, int id) { yield return new ReferenceToken(name, id); } private IEnumerator PrimitiveEnumerator(string name, int typeId, ISerialData serializedValue, int? id, bool emitTypeInfo) { if (emitTypeInfo) { var mapping = TypeCache[typeId]; yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer); } yield return new PrimitiveToken(name, typeId, id, serializedValue); } private IEnumerator CompositeEnumerator( string name, IEnumerable tags, int? id, int typeId, IEnumerable metaInfo, bool emitTypeInfo) { if (emitTypeInfo) { var mapping = TypeCache[typeId]; yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer); } yield return new BeginToken(name, typeId, id); bool first = true; if (metaInfo != null) { foreach (var tag in metaInfo) { IEnumerator metaIt = Serialize(new DataMemberAccessor(tag.Value, tag.Name)); while (metaIt.MoveNext()) { if (first) { yield return new MetaInfoBeginToken(); first = false; } yield return metaIt.Current; } } } if (!first) { yield return new MetaInfoEndToken(); } if (tags != null) { foreach (var tag in tags) { IEnumerator it = Serialize(new DataMemberAccessor(tag.Value, tag.Name)); while (it.MoveNext()) yield return it.Current; } } yield return new EndToken(name, typeId, id); } } }