Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 15682 was 15583, checked in by swagner, 7 years ago

#2640: Updated year of copyrights in license headers

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