Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 16713 was 16565, checked in by gkronber, 6 years ago

#2520: merged changes from PersistenceOverhaul branch (r16451:16564) into trunk

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