1 | #region License Information |
---|
2 | /* HeuristicLab |
---|
3 | * Copyright (C) 2002-2011 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 | |
---|
22 | using System; |
---|
23 | using System.Collections; |
---|
24 | using System.Collections.Generic; |
---|
25 | using System.Reflection; |
---|
26 | using System.Text; |
---|
27 | using HeuristicLab.Persistence.Auxiliary; |
---|
28 | using HeuristicLab.Persistence.Core.Tokens; |
---|
29 | using HeuristicLab.Persistence.Interfaces; |
---|
30 | |
---|
31 | namespace 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(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(string name, object obj) { |
---|
211 | |
---|
212 | object value = obj; |
---|
213 | if (value == null) |
---|
214 | return NullReferenceEnumerator(name); |
---|
215 | Type type = value.GetType(); |
---|
216 | if (obj2id.ContainsKey(value)) |
---|
217 | return ReferenceEnumerator(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(name); |
---|
231 | IPrimitiveSerializer primitiveSerializer = configuration.GetPrimitiveSerializer(type); |
---|
232 | if (primitiveSerializer != null) |
---|
233 | return PrimitiveEnumerator( |
---|
234 | 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 | 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."); |
---|
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(type, "Uncaught exception during serialization: " + x.Message); |
---|
258 | } |
---|
259 | } |
---|
260 | finally { |
---|
261 | objectGraphTrace.Pop(); |
---|
262 | } |
---|
263 | } |
---|
264 | |
---|
265 | private PersistenceException CreatePersistenceException(Type type, string message) { |
---|
266 | StringBuilder sb = new StringBuilder(); |
---|
267 | sb.Append(message) |
---|
268 | .Append("Type was \"") |
---|
269 | .Append(type.VersionInvariantName()) |
---|
270 | .AppendLine("\"") |
---|
271 | .Append("object graph location: ") |
---|
272 | .AppendLine(string.Join(".", objectGraphTrace.ToArray())) |
---|
273 | .AppendLine("No registered primitive serializer for this type:"); |
---|
274 | foreach (var ps in configuration.PrimitiveSerializers) |
---|
275 | sb.Append(ps.SourceType.VersionInvariantName()) |
---|
276 | .Append(" ---- (") |
---|
277 | .Append(ps.GetType().VersionInvariantName()) |
---|
278 | .AppendLine(")"); |
---|
279 | sb.AppendLine("Rejected by all composite serializers:"); |
---|
280 | foreach (var cs in configuration.CompositeSerializers) |
---|
281 | sb.Append("\"") |
---|
282 | .Append(cs.JustifyRejection(type)) |
---|
283 | .Append("\" ---- (") |
---|
284 | .Append(cs.GetType().VersionInvariantName()) |
---|
285 | .AppendLine(")"); |
---|
286 | return new PersistenceException(sb.ToString()); |
---|
287 | } |
---|
288 | |
---|
289 | private IEnumerator<ISerializationToken> NullReferenceEnumerator(string name) { |
---|
290 | yield return new NullReferenceToken(name); |
---|
291 | } |
---|
292 | |
---|
293 | private IEnumerator<ISerializationToken> ReferenceEnumerator(string name, int id) { |
---|
294 | yield return new ReferenceToken(name, id); |
---|
295 | } |
---|
296 | |
---|
297 | private IEnumerator<ISerializationToken> PrimitiveEnumerator(string name, |
---|
298 | int typeId, ISerialData serializedValue, int? id, bool emitTypeInfo) { |
---|
299 | if (emitTypeInfo) { |
---|
300 | var mapping = TypeCache[typeId]; |
---|
301 | yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer); |
---|
302 | } |
---|
303 | yield return new PrimitiveToken(name, typeId, id, serializedValue); |
---|
304 | } |
---|
305 | |
---|
306 | private IEnumerator<ISerializationToken> CompositeEnumerator( |
---|
307 | string name, IEnumerable<Tag> tags, int? id, int typeId, IEnumerable<Tag> metaInfo, |
---|
308 | bool emitTypeInfo) { |
---|
309 | if (emitTypeInfo) { |
---|
310 | var mapping = TypeCache[typeId]; |
---|
311 | yield return new TypeToken(mapping.Id, mapping.TypeName, mapping.Serializer); |
---|
312 | } |
---|
313 | yield return new BeginToken(name, typeId, id); |
---|
314 | bool first = true; |
---|
315 | if (metaInfo != null) { |
---|
316 | foreach (var tag in metaInfo) { |
---|
317 | IEnumerator<ISerializationToken> metaIt = Serialize(tag.Name, tag.Value); |
---|
318 | while (metaIt.MoveNext()) { |
---|
319 | if (first) { |
---|
320 | yield return new MetaInfoBeginToken(); |
---|
321 | first = false; |
---|
322 | } |
---|
323 | yield return metaIt.Current; |
---|
324 | } |
---|
325 | } |
---|
326 | } |
---|
327 | if (!first) { |
---|
328 | yield return new MetaInfoEndToken(); |
---|
329 | } |
---|
330 | if (tags != null) { |
---|
331 | foreach (var tag in tags) { |
---|
332 | IEnumerator<ISerializationToken> it = Serialize(tag.Name, tag.Value); |
---|
333 | while (it.MoveNext()) |
---|
334 | yield return it.Current; |
---|
335 | } |
---|
336 | } |
---|
337 | yield return new EndToken(name, typeId, id); |
---|
338 | } |
---|
339 | } |
---|
340 | } |
---|