Free cookie consent management tool by TermsFeed Policy Generator

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

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

more descriptive persistence error message (#548)

File size: 9.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    public bool InterleaveTypeInformation { get; set; }
54
55    /// <summary>
56    /// Contains a mapping of type id to type and serializer.
57    /// </summary>
58    public List<TypeMapping> TypeCache {
59      get {
60        BuildTypeCache();
61        return externalTypeCache;
62      }
63    }
64   
65    /// <summary>
66    /// Contains a list of files (mostly assemblies) that are
67    /// necessary to deserialize the object graph again.   
68    /// </summary>
69    public List<string> RequiredFiles {
70      get {
71        BuildTypeCache();
72        return requiredFiles;
73      }
74    }
75
76    private List<TypeMapping> externalTypeCache;
77    private List<string> requiredFiles;
78    private void BuildTypeCache() {
79      externalTypeCache = new List<TypeMapping>();
80      Dictionary<Assembly, bool> assemblies = new Dictionary<Assembly, bool>();
81      foreach (var pair in typeCache) {
82        string serializer = null;
83        IPrimitiveSerializer f = configuration.GetPrimitiveSerializer(pair.Key);
84        if (f != null) {
85          serializer = f.GetType().AssemblyQualifiedName;
86          assemblies[f.GetType().Assembly] = true;
87        } else {
88          ICompositeSerializer d = configuration.GetCompositeSerializer(pair.Key);
89          serializer = d.GetType().AssemblyQualifiedName;
90          assemblies[d.GetType().Assembly] = true;
91        }
92        externalTypeCache.Add(new TypeMapping(pair.Value, pair.Key.AssemblyQualifiedName, serializer));
93        assemblies[pair.Key.Assembly] = true;
94      }
95      Dictionary<string, bool> files = new Dictionary<string, bool>();
96      foreach (Assembly a in assemblies.Keys) {
97        files[a.CodeBase] = true;
98      }
99      requiredFiles = new List<string>(files.Keys);
100    }
101   
102    public Serializer(object obj, Configuration configuration) :
103      this(obj, configuration, "ROOT") { }
104
105    public Serializer(object obj, Configuration configuration, string rootName)
106      : this(obj, configuration, rootName, false) { }
107
108    /// <param name="isTestRun">Try to complete the whole object graph,
109    /// don't stop at the first exception</param>
110    public Serializer(object obj, Configuration configuration, string rootName, bool isTestRun) {
111      this.InterleaveTypeInformation = false;
112      this.obj = obj;
113      this.rootName = rootName;
114      this.configuration = configuration;
115      obj2id = new Dictionary<object, int>(new ReferenceEqualityComparer()) { { new object(), 0 } };
116      typeCache = new Dictionary<Type, int>();
117      this.isTestRun = isTestRun;
118      this.exceptions = new List<Exception>();
119    }
120
121    IEnumerator IEnumerable.GetEnumerator() {
122      return GetEnumerator();
123    }
124
125    public IEnumerator<ISerializationToken> GetEnumerator() {
126      var enumerator = Serialize(new DataMemberAccessor(rootName, null, () => obj, null));
127      if (isTestRun) {
128        return AddExceptionCompiler(enumerator);
129      } else {
130        return enumerator;
131      }
132    }
133
134    private IEnumerator<ISerializationToken> AddExceptionCompiler(IEnumerator<ISerializationToken> enumerator) {
135      while (enumerator.MoveNext())
136        yield return enumerator.Current;
137      if (exceptions.Count == 1)
138        throw exceptions[0];
139      if (exceptions.Count > 1)
140        throw new PersistenceException("Multiple exceptions during serialization", exceptions);
141    }
142
143    private Stack<string> objectGraphTrace = new Stack<string>();
144
145    private IEnumerator<ISerializationToken> Serialize(DataMemberAccessor accessor) {
146     
147      object value = accessor.Get();
148      if (value == null)
149        return NullReferenceEnumerator(accessor.Name);
150      Type type = value.GetType();
151      if (obj2id.ContainsKey(value))
152        return ReferenceEnumerator(accessor.Name, obj2id[value]);
153      bool emitTypeInfo = false;
154      if (!typeCache.ContainsKey(type)) {
155        typeCache.Add(type, typeCache.Count);
156        emitTypeInfo = InterleaveTypeInformation;
157      }
158      int typeId = typeCache[type];
159      int? id = null;
160      if (!type.IsValueType) {
161        id = obj2id.Count;
162        obj2id.Add(value, (int)id);
163      }
164      try {
165        objectGraphTrace.Push(accessor.Name);
166        IPrimitiveSerializer primitiveSerializer = configuration.GetPrimitiveSerializer(type);
167        if (primitiveSerializer != null)
168          return PrimitiveEnumerator(
169            accessor.Name,
170            typeId,
171            primitiveSerializer.Format(value),
172            id,
173            emitTypeInfo);
174        ICompositeSerializer compositeSerializer = configuration.GetCompositeSerializer(type);
175        if (compositeSerializer != null)
176          return CompositeEnumerator(
177            accessor.Name,
178            compositeSerializer.Decompose(value),
179            id,
180            typeId,
181            compositeSerializer.CreateMetaInfo(value),
182            emitTypeInfo);
183        throw CreatePersistenceException(type);
184      } catch (Exception x) {
185        if (isTestRun) {
186          exceptions.Add(x);
187          return new List<ISerializationToken>().GetEnumerator();
188        } else {
189          throw;
190        }
191      } finally {
192        objectGraphTrace.Pop();
193      }
194    }
195
196    private PersistenceException CreatePersistenceException(Type type) {
197      StringBuilder sb = new StringBuilder();
198      sb.Append("Could not determine how to serialize a value of type \"")
199        .Append(type.VersionInvariantName())
200        .AppendLine("\"")
201        .Append("object graph location: ")
202        .AppendLine(string.Join(".", objectGraphTrace.ToArray()))
203        .AppendLine("No registered primitive serializer for this type:");
204      foreach (var ps in configuration.PrimitiveSerializers)
205        sb.Append(ps.SourceType.VersionInvariantName())
206          .Append(" ---- (")
207          .Append(ps.GetType().VersionInvariantName())
208          .AppendLine(")");         
209      sb.AppendLine("Rejected by all composite serializers:");
210      foreach (var cs in configuration.CompositeSerializers)
211        sb.Append("\"")
212          .Append(cs.JustifyRejection(type))
213          .Append("\" ---- (")
214          .Append(cs.GetType().VersionInvariantName())
215          .AppendLine(")");
216      return new PersistenceException(sb.ToString());
217    }
218
219    private IEnumerator<ISerializationToken> NullReferenceEnumerator(string name) {
220      yield return new NullReferenceToken(name);
221    }
222
223    private IEnumerator<ISerializationToken> ReferenceEnumerator(string name, int id) {
224      yield return new ReferenceToken(name, id);
225    }
226
227    private IEnumerator<ISerializationToken> PrimitiveEnumerator(string name,
228        int typeId, ISerialData serializedValue, int? id, bool emitTypeInfo) {
229      if (emitTypeInfo) {
230        var mapping = TypeCache[typeId];
231        yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer);
232      }
233      yield return new PrimitiveToken(name, typeId, id, serializedValue);
234    }
235
236    private IEnumerator<ISerializationToken> CompositeEnumerator(
237        string name, IEnumerable<Tag> tags, int? id, int typeId, IEnumerable<Tag> metaInfo,
238        bool emitTypeInfo) {
239      if (emitTypeInfo) {
240        var mapping = TypeCache[typeId];
241        yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer);
242      }
243      yield return new BeginToken(name, typeId, id);
244      bool first = true;
245      if (metaInfo != null) {
246        foreach (var tag in metaInfo) {
247          IEnumerator<ISerializationToken> metaIt = Serialize(new DataMemberAccessor(tag.Value, tag.Name));
248          while (metaIt.MoveNext()) {
249            if (first) {
250              yield return new MetaInfoBeginToken();
251              first = false;
252            }
253            yield return metaIt.Current;
254          }
255        }
256      }
257      if (!first) {
258        yield return new MetaInfoEndToken();
259      }
260      if (tags != null) {
261        foreach (var tag in tags) {
262          IEnumerator<ISerializationToken> it = Serialize(new DataMemberAccessor(tag.Value, tag.Name));
263          while (it.MoveNext())
264            yield return it.Current;
265        }
266      }
267      yield return new EndToken(name, typeId, id);
268    }
269
270  }
271
272
273}
Note: See TracBrowser for help on using the repository browser.