Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Persistence/3.3/Core/Serializer.cs @ 3577

Last change on this file since 3577 was 3577, checked in by epitzer, 14 years ago

throw Exception if class is marked [Storable] but cannot be serialized with the StorableSerializer (#548)

File size: 11.8 KB
Line 
1using System.Collections.Generic;
2using System.Collections;
3using System;
4using System.Linq;
5using HeuristicLab.Persistence.Auxiliary;
6using HeuristicLab.Persistence.Interfaces;
7using HeuristicLab.Persistence.Core.Tokens;
8using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
9using System.Text;
10using System.Reflection;
11using System.IO;
12using System.Diagnostics;
13
14namespace HeuristicLab.Persistence.Core {
15
16  /// <summary>
17  /// The core hub for serialization. This class transforms an object graph
18  /// into a tree and later into a stream of serialization tokens using
19  /// the given configuration.
20  ///
21  /// <para>Primitive serializers directly format an object to a serializable type.</para>
22  ///
23  /// <para>Composite serializers decompose an object into other object that are then
24  /// recursively serialized.</para> 
25  ///
26  /// A constructed serializer is enumerable and continuously analyses
27  /// and traverses the object graph while the enumerator is iterated
28  /// </summary> 
29  public class Serializer : IEnumerable<ISerializationToken> {
30
31    private class ReferenceEqualityComparer : IEqualityComparer<object> {
32
33      public new bool Equals(object a, object b) {
34        return Object.ReferenceEquals(a, b);
35      }
36
37      public int GetHashCode(object obj) {
38        if (obj == null)
39          return 0;
40        return obj.GetHashCode();
41      }
42
43    }
44
45    private readonly object obj;
46    private readonly string rootName;
47    private readonly Dictionary<object, int> obj2id;
48    private readonly Dictionary<Type, int> typeCache;
49    private readonly Configuration configuration;
50    private readonly bool isTestRun;
51    private readonly List<Exception> exceptions;
52
53    /// <summary>
54    /// Gets or sets a value indicating whether to interleave type information
55    /// while serializing an object.
56    ///
57    /// Alternatively the type information can be obtained through the
58    /// <see cref="TypeCache"/> Property after serialization is done.
59    /// </summary>
60    /// <value>
61    ///   <c>true</c> if type information should be interleaved; otherwise, <c>false</c>.
62    /// </value>
63    public bool InterleaveTypeInformation { get; set; }
64
65    /// <summary>
66    /// Contains a mapping of type id to type and serializer.
67    /// </summary>
68    /// <value>The type cache.</value>
69    public List<TypeMapping> TypeCache {
70      get {
71        BuildTypeCache();
72        return externalTypeCache;
73      }
74    }
75   
76    /// <summary>
77    /// Contains a list of files (mostly assemblies) that are
78    /// necessary to deserialize the object graph again.   
79    /// </summary>
80    public List<string> RequiredFiles {
81      get {
82        BuildTypeCache();
83        return requiredFiles;
84      }
85    }
86
87    private List<TypeMapping> externalTypeCache;
88    private List<string> requiredFiles;
89    private void BuildTypeCache() {
90      externalTypeCache = new List<TypeMapping>();
91      Dictionary<Assembly, bool> assemblies = new Dictionary<Assembly, bool>();
92      foreach (var pair in typeCache) {
93        string serializer = null;
94        IPrimitiveSerializer f = configuration.GetPrimitiveSerializer(pair.Key);
95        if (f != null) {
96          serializer = f.GetType().AssemblyQualifiedName;
97          assemblies[f.GetType().Assembly] = true;
98        } else {
99          ICompositeSerializer d = configuration.GetCompositeSerializer(pair.Key);
100          serializer = d.GetType().AssemblyQualifiedName;
101          assemblies[d.GetType().Assembly] = true;
102        }
103        externalTypeCache.Add(new TypeMapping(pair.Value, pair.Key.AssemblyQualifiedName, serializer));
104        assemblies[pair.Key.Assembly] = true;
105      }
106      Dictionary<string, bool> files = new Dictionary<string, bool>();
107      foreach (Assembly a in assemblies.Keys) {
108        files[a.CodeBase] = true;
109      }
110      requiredFiles = new List<string>(files.Keys);
111    }
112
113    /// <summary>
114    /// Initializes a new instance of the <see cref="Serializer"/> class.
115    /// </summary>
116    /// <param name="obj">The object to serialize.</param>
117    /// <param name="configuration">The configuration.</param>
118    public Serializer(object obj, Configuration configuration) :
119      this(obj, configuration, "ROOT") { }
120
121    /// <summary>
122    /// Initializes a new instance of the <see cref="Serializer"/> class.
123    /// </summary>
124    /// <param name="obj">The object to serialize.</param>
125    /// <param name="configuration">The configuration.</param>
126    /// <param name="rootName">Name of the root token.</param>
127    public Serializer(object obj, Configuration configuration, string rootName)
128      : this(obj, configuration, rootName, false) { }
129
130    /// <summary>
131    /// Initializes a new instance of the <see cref="Serializer"/> class.
132    /// </summary>
133    /// <param name="obj">The object to serialize.</param>
134    /// <param name="configuration">The configuration.</param>
135    /// <param name="rootName">Name of the root token.</param>
136    /// <param name="isTestRun">Try to complete the whole object graph,
137    /// don't stop at the first exception</param>
138    public Serializer(object obj, Configuration configuration, string rootName, bool isTestRun) {
139      this.InterleaveTypeInformation = false;
140      this.obj = obj;
141      this.rootName = rootName;
142      this.configuration = configuration;
143      obj2id = new Dictionary<object, int>(new ReferenceEqualityComparer()) { { new object(), 0 } };
144      typeCache = new Dictionary<Type, int>();
145      this.isTestRun = isTestRun;
146      this.exceptions = new List<Exception>();
147    }
148
149    /// <summary>
150    /// Returns an enumerator that iterates through a collection.
151    /// </summary>
152    /// <returns>
153    /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to
154    /// iterate through the collection.
155    /// </returns>
156    IEnumerator IEnumerable.GetEnumerator() {
157      return GetEnumerator();
158    }
159
160    /// <summary>
161    /// Returns an enumerator that iterates through the serialization tokens.
162    /// </summary>
163    /// <returns>
164    /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to
165    /// iterate through serialization tokens.
166    /// </returns>
167    public IEnumerator<ISerializationToken> GetEnumerator() {
168      var enumerator = Serialize(new DataMemberAccessor(rootName, null, () => obj, null));
169      if (isTestRun) {
170        return AddExceptionCompiler(enumerator);
171      } else {
172        return enumerator;
173      }
174    }
175
176    private IEnumerator<ISerializationToken> AddExceptionCompiler(IEnumerator<ISerializationToken> enumerator) {
177      while (enumerator.MoveNext())
178        yield return enumerator.Current;
179      if (exceptions.Count == 1)
180        throw exceptions[0];
181      if (exceptions.Count > 1)
182        throw new PersistenceException("Multiple exceptions during serialization", exceptions);
183    }
184
185    private Stack<string> objectGraphTrace = new Stack<string>();
186
187    private IEnumerator<ISerializationToken> Serialize(DataMemberAccessor accessor) {
188     
189      object value = accessor.Get();
190      if (value == null)
191        return NullReferenceEnumerator(accessor.Name);
192      Type type = value.GetType();
193      if (obj2id.ContainsKey(value))
194        return ReferenceEnumerator(accessor.Name, obj2id[value]);
195      bool emitTypeInfo = false;
196      if (!typeCache.ContainsKey(type)) {
197        typeCache.Add(type, typeCache.Count);
198        emitTypeInfo = InterleaveTypeInformation;
199      }
200      int typeId = typeCache[type];
201      int? id = null;
202      if (!type.IsValueType) {
203        id = obj2id.Count;
204        obj2id.Add(value, (int)id);
205      }
206      try {
207        objectGraphTrace.Push(accessor.Name);
208        IPrimitiveSerializer primitiveSerializer = configuration.GetPrimitiveSerializer(type);
209        if (primitiveSerializer != null)
210          return PrimitiveEnumerator(
211            accessor.Name,
212            typeId,
213            primitiveSerializer.Format(value),
214            id,
215            emitTypeInfo);
216        ICompositeSerializer compositeSerializer = configuration.GetCompositeSerializer(type);
217        if (compositeSerializer != null)
218          return CompositeEnumerator(
219            accessor.Name,
220            compositeSerializer.Decompose(value),
221            id,
222            typeId,
223            compositeSerializer.CreateMetaInfo(value),
224            emitTypeInfo);
225        throw CreatePersistenceException(type, "Could not determine how to serialize a value.");     
226      } catch (Exception x) {
227        if (isTestRun) {
228          exceptions.Add(x);
229          return new List<ISerializationToken>().GetEnumerator();
230        } else if (x is PersistenceException) {
231          throw;
232        } else {
233          throw CreatePersistenceException(type, "Uncaught exception during serialization: " + x.Message);
234        }
235      } finally {
236        objectGraphTrace.Pop();
237      }
238    }
239
240    private PersistenceException CreatePersistenceException(Type type, string message) {
241      StringBuilder sb = new StringBuilder();
242      sb.Append(message)
243        .Append("Type was \"")
244        .Append(type.VersionInvariantName())
245        .AppendLine("\"")
246        .Append("object graph location: ")
247        .AppendLine(string.Join(".", objectGraphTrace.ToArray()))
248        .AppendLine("No registered primitive serializer for this type:");
249      foreach (var ps in configuration.PrimitiveSerializers)
250        sb.Append(ps.SourceType.VersionInvariantName())
251          .Append(" ---- (")
252          .Append(ps.GetType().VersionInvariantName())
253          .AppendLine(")");
254      sb.AppendLine("Rejected by all composite serializers:");
255      foreach (var cs in configuration.CompositeSerializers)
256        sb.Append("\"")
257          .Append(cs.JustifyRejection(type))
258          .Append("\" ---- (")
259          .Append(cs.GetType().VersionInvariantName())
260          .AppendLine(")");
261      return new PersistenceException(sb.ToString());
262    }
263
264    private IEnumerator<ISerializationToken> NullReferenceEnumerator(string name) {
265      yield return new NullReferenceToken(name);
266    }
267
268    private IEnumerator<ISerializationToken> ReferenceEnumerator(string name, int id) {
269      yield return new ReferenceToken(name, id);
270    }
271
272    private IEnumerator<ISerializationToken> PrimitiveEnumerator(string name,
273        int typeId, ISerialData serializedValue, int? id, bool emitTypeInfo) {
274      if (emitTypeInfo) {
275        var mapping = TypeCache[typeId];
276        yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer);
277      }
278      yield return new PrimitiveToken(name, typeId, id, serializedValue);
279    }
280
281    private IEnumerator<ISerializationToken> CompositeEnumerator(
282        string name, IEnumerable<Tag> tags, int? id, int typeId, IEnumerable<Tag> metaInfo,
283        bool emitTypeInfo) {
284      if (emitTypeInfo) {
285        var mapping = TypeCache[typeId];
286        yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer);
287      }
288      yield return new BeginToken(name, typeId, id);
289      bool first = true;
290      if (metaInfo != null) {
291        foreach (var tag in metaInfo) {
292          IEnumerator<ISerializationToken> metaIt = Serialize(new DataMemberAccessor(tag.Value, tag.Name));
293          while (metaIt.MoveNext()) {
294            if (first) {
295              yield return new MetaInfoBeginToken();
296              first = false;
297            }
298            yield return metaIt.Current;
299          }
300        }
301      }
302      if (!first) {
303        yield return new MetaInfoEndToken();
304      }
305      if (tags != null) {
306        foreach (var tag in tags) {
307          IEnumerator<ISerializationToken> it = Serialize(new DataMemberAccessor(tag.Value, tag.Name));
308          while (it.MoveNext())
309            yield return it.Current;
310        }
311      }
312      yield return new EndToken(name, typeId, id);
313    }
314  }
315}
Note: See TracBrowser for help on using the repository browser.