#region License Information /* HeuristicLab * Copyright (C) 2002-2011 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; using System.Collections.Generic; using System.Linq; using System.Text; using HeuristicLab.Persistence.Auxiliary; using HeuristicLab.Persistence.Core.Tokens; using HeuristicLab.Persistence.Interfaces; 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 sealed class Serializer : IEnumerable { #region Nested Types 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(); } } #endregion #region Fields private readonly object obj; private readonly string rootName; private readonly Dictionary obj2id; private readonly Configuration configuration; private readonly bool isTestRun; private readonly List exceptions; private readonly Stack objectGraphTrace; private readonly CachedTypeSerializer typeSerializer; private readonly TypeCache typeCache; #endregion /// /// 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; } public TypeCache TypeCache { get { return typeCache; } } /// /// Contains a list of files (mostly assemblies) that are /// necessary to deserialize the object graph again. /// public IEnumerable GetRequiredFiles() { HashSet files = new HashSet(); foreach (var t in typeCache.Types) { files.Add(t.Assembly.CodeBase); Type serializer = typeCache.GetSerializer(typeCache.GetOrCreateId(t)); if (serializer != null) files.Add(serializer.Assembly.CodeBase); } return files; } public IEnumerable SerializedTypes { get { return typeCache.Types; } } /// /// 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; obj2id = new Dictionary(new ReferenceEqualityComparer()) { { new object(), 0 } }; typeCache = new TypeCache(); this.isTestRun = isTestRun; this.exceptions = new List(); this.objectGraphTrace = new Stack(); this.typeSerializer = new CachedTypeSerializer(typeCache); this.configuration = new Configuration( configuration.Format, configuration.PrimitiveSerializers, new[] { typeSerializer }.Concat(configuration.CompositeSerializers)); } /// /// 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(rootName, obj); 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 IEnumerator Serialize(string name, object obj) { object value = obj; if (value == null) return NullReferenceEnumerator(name); Type type = value.GetType(); if (obj2id.ContainsKey(value)) return ReferenceEnumerator(name, obj2id[value]); int typeId = typeCache.GetOrCreateId(type); int? id = null; if (!type.IsValueType) { id = obj2id.Count; obj2id.Add(value, (int)id); } try { objectGraphTrace.Push(name); IPrimitiveSerializer primitiveSerializer = configuration.GetPrimitiveSerializer(type); if (primitiveSerializer != null) { typeCache.SetSerializer(typeId, primitiveSerializer.GetType()); return PrimitiveEnumerator( name, typeId, primitiveSerializer.Format(value), id); } ICompositeSerializer compositeSerializer = configuration.GetCompositeSerializer(type); typeCache.SetSerializer(typeId, compositeSerializer.GetType()); if (compositeSerializer != null) return CompositeEnumerator( name, compositeSerializer.Decompose(value), id, typeId, compositeSerializer.CreateMetaInfo(value)); throw CreatePersistenceException(type, "Could not determine how to serialize a value.", null); } catch (Exception x) { if (isTestRun) { exceptions.Add(x); return new List().GetEnumerator(); } else if (x is PersistenceException) { throw; } else { throw CreatePersistenceException(type, "Uncaught exception during serialization: ", x); } } finally { objectGraphTrace.Pop(); } } private PersistenceException CreatePersistenceException(Type type, string message, Exception x) { StringBuilder sb = new StringBuilder(); sb.Append(message) .Append("Type was \"") .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) { if (InterleaveTypeInformation) { if (typeCache.HasUnpublishedType) yield return typeCache.PublishUnpublishedType(); if (typeCache.HasUnpublishedSerializer) yield return typeCache.PublishUnpublishedSerializer(); } yield return new PrimitiveToken(name, typeId, id, serializedValue); } private IEnumerator CompositeEnumerator(string name, IEnumerable tags, int? id, int typeId, IEnumerable metaInfo) { if (InterleaveTypeInformation) { if (typeCache.HasUnpublishedType) yield return typeCache.PublishUnpublishedType(); if (typeCache.HasUnpublishedSerializer) yield return typeCache.PublishUnpublishedSerializer(); } yield return new BeginToken(name, typeId, id); bool first = true; if (metaInfo != null) { foreach (var tag in metaInfo) { IEnumerator metaIt = Serialize(tag.Name, tag.Value); 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(tag.Name, tag.Value); while (it.MoveNext()) yield return it.Current; } } yield return new EndToken(); } } }