Free cookie consent management tool by TermsFeed Policy Generator

source: branches/PersistenceReintegration/HeuristicLab.Persistence/3.3/Default/Xml/XmlGenerator.cs @ 14928

Last change on this file since 14928 was 14927, checked in by gkronber, 8 years ago

#2520: changed all usages of StorableClass to use StorableType with an auto-generated GUID (did not add StorableType to other type definitions yet)

File size: 18.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 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.Generic;
24using System.IO;
25using System.IO.Compression;
26using System.Linq;
27using System.Text;
28using HeuristicLab.Persistence.Core;
29using HeuristicLab.Persistence.Core.Tokens;
30using HeuristicLab.Persistence.Interfaces;
31using HeuristicLab.Tracing;
32
33namespace HeuristicLab.Persistence.Default.Xml {
34
35
36  /// <summary>
37  /// Main entry point of persistence to XML. Use the static methods to serialize
38  /// to a file or to a stream.
39  /// </summary>
40  public class XmlGenerator : GeneratorBase<string> {
41
42    protected int depth;
43    protected int Depth {
44      get {
45        return depth;
46      }
47      set {
48        depth = value;
49        prefix = new string(' ', depth * 2);
50      }
51    }
52
53    protected string prefix;
54
55
56    /// <summary>
57    /// Initializes a new instance of the <see cref="XmlGenerator"/> class.
58    /// </summary>
59    public XmlGenerator() {
60      Depth = 0;
61    }
62
63    protected enum NodeType { Start, End, Inline };
64
65    protected static void AddXmlTagContent(StringBuilder sb, string name, Dictionary<string, string> attributes) {
66      sb.Append(name);
67      foreach (var attribute in attributes) {
68        if (attribute.Value != null && !string.IsNullOrEmpty(attribute.Value.ToString())) {
69          sb.Append(' ');
70          sb.Append(attribute.Key);
71          sb.Append("=\"");
72          sb.Append(attribute.Value);
73          sb.Append('"');
74        }
75      }
76    }
77
78    protected static int AttributeLength(Dictionary<string, string> attributes) {
79      return attributes
80        .Where(kvp => !string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(kvp.Value))
81        .Select(kvp => kvp.Key.Length + kvp.Value.Length + 4).Sum();
82    }
83
84    protected static void AddXmlStartTag(StringBuilder sb, string name, Dictionary<string, string> attributes) {
85      sb.Append('<');
86      AddXmlTagContent(sb, name, attributes);
87      sb.Append('>');
88    }
89
90    protected static void AddXmlInlineTag(StringBuilder sb, string name, Dictionary<string, string> attributes) {
91      sb.Append('<');
92      AddXmlTagContent(sb, name, attributes);
93      sb.Append("/>");
94    }
95
96    protected static void AddXmlEndTag(StringBuilder sb, string name) {
97      sb.Append("</");
98      sb.Append(name);
99      sb.Append(">");
100    }
101
102    protected string CreateNodeStart(string name, Dictionary<string, string> attributes) {
103      StringBuilder sb = new StringBuilder(prefix.Length + name.Length + 4
104        + AttributeLength(attributes));
105      sb.Append(prefix);
106      Depth += 1;
107      AddXmlStartTag(sb, name, attributes);
108      sb.Append("\r\n");
109      return sb.ToString();
110    }
111
112    private static Dictionary<string, string> emptyDict = new Dictionary<string, string>();
113    protected string CreateNodeStart(string name) {
114      return CreateNodeStart(name, emptyDict);
115    }
116
117    protected string CreateNodeEnd(string name) {
118      Depth -= 1;
119      StringBuilder sb = new StringBuilder(prefix.Length + name.Length + 5);
120      sb.Append(prefix);
121      AddXmlEndTag(sb, name);
122      sb.Append("\r\n");
123      return sb.ToString();
124    }
125
126    protected string CreateNode(string name, Dictionary<string, string> attributes) {
127      StringBuilder sb = new StringBuilder(prefix.Length + name.Length + 5
128        + AttributeLength(attributes));
129      sb.Append(prefix);
130      AddXmlInlineTag(sb, name, attributes);
131      sb.Append("\r\n");
132      return sb.ToString();
133    }
134
135    protected string CreateNode(string name, Dictionary<string, string> attributes, string content) {
136      StringBuilder sb = new StringBuilder(
137        prefix.Length + name.Length + AttributeLength(attributes) + 2
138        + content.Length + name.Length + 5);
139      sb.Append(prefix);
140      AddXmlStartTag(sb, name, attributes);
141      sb.Append(content);
142      sb.Append("</").Append(name).Append(">\r\n");
143      return sb.ToString();
144    }
145
146    /// <summary>
147    /// Formats the specified begin token.
148    /// </summary>
149    /// <param name="beginToken">The begin token.</param>
150    /// <returns>The token in serialized form.</returns>
151    protected override string Format(BeginToken beginToken) {
152      var dict = new Dictionary<string, string> {
153          {"name", beginToken.Name},
154          {"typeId", beginToken.TypeId.ToString()},
155          {"id", beginToken.Id.ToString()}};
156      AddTypeInfo(beginToken.TypeId, dict);
157      return CreateNodeStart(XmlStringConstants.COMPOSITE, dict);
158
159    }
160
161    protected void AddTypeInfo(int typeId, Dictionary<string, string> dict) {
162      if (lastTypeToken != null) {
163        if (typeId == lastTypeToken.Id) {
164          dict.Add("typeName", lastTypeToken.TypeName);
165          dict.Add("serializer", lastTypeToken.Serializer);
166          lastTypeToken = null;
167        } else {
168          FlushTypeToken();
169        }
170      }
171    }
172
173    /// <summary>
174    /// Formats the specified end token.
175    /// </summary>
176    /// <param name="endToken">The end token.</param>
177    /// <returns>The token in serialized form.</returns>
178    protected override string Format(EndToken endToken) {
179      return CreateNodeEnd(XmlStringConstants.COMPOSITE);
180    }
181
182    /// <summary>
183    /// Formats the specified data token.
184    /// </summary>
185    /// <param name="dataToken">The data token.</param>
186    /// <returns>The token in serialized form.</returns>
187    protected override string Format(PrimitiveToken dataToken) {
188      var dict = new Dictionary<string, string> {
189            {"typeId", dataToken.TypeId.ToString()},
190            {"name", dataToken.Name},
191            {"id", dataToken.Id.ToString()}};
192      AddTypeInfo(dataToken.TypeId, dict);
193      return CreateNode(XmlStringConstants.PRIMITIVE, dict,
194        ((XmlString)dataToken.SerialData).Data);
195    }
196
197    /// <summary>
198    /// Formats the specified ref token.
199    /// </summary>
200    /// <param name="refToken">The ref token.</param>
201    /// <returns>The token in serialized form.</returns>
202    protected override string Format(ReferenceToken refToken) {
203      return CreateNode(XmlStringConstants.REFERENCE,
204        new Dictionary<string, string> {
205          {"ref", refToken.Id.ToString()},
206          {"name", refToken.Name}});
207    }
208
209    /// <summary>
210    /// Formats the specified null ref token.
211    /// </summary>
212    /// <param name="nullRefToken">The null ref token.</param>
213    /// <returns>The token in serialized form.</returns>
214    protected override string Format(NullReferenceToken nullRefToken) {
215      return CreateNode(XmlStringConstants.NULL,
216        new Dictionary<string, string>{
217          {"name", nullRefToken.Name}});
218    }
219
220    /// <summary>
221    /// Formats the specified meta info begin token.
222    /// </summary>
223    /// <param name="metaInfoBeginToken">The meta info begin token.</param>
224    /// <returns>The token in serialized form.</returns>
225    protected override string Format(MetaInfoBeginToken metaInfoBeginToken) {
226      return CreateNodeStart(XmlStringConstants.METAINFO);
227    }
228
229    /// <summary>
230    /// Formats the specified meta info end token.
231    /// </summary>
232    /// <param name="metaInfoEndToken">The meta info end token.</param>
233    /// <returns>The token in serialized form.</returns>
234    protected override string Format(MetaInfoEndToken metaInfoEndToken) {
235      return CreateNodeEnd(XmlStringConstants.METAINFO);
236    }
237
238    protected TypeToken lastTypeToken;
239    /// <summary>
240    /// Formats the specified token.
241    /// </summary>
242    /// <param name="token">The token.</param>
243    /// <returns>The token in serialized form.</returns>
244    protected override string Format(TypeToken token) {
245      lastTypeToken = token;
246      return "";
247    }
248
249    protected string FlushTypeToken() {
250      if (lastTypeToken == null)
251        return "";
252      try {
253        return CreateNode(XmlStringConstants.TYPE,
254          new Dictionary<string, string> {
255          {"id", lastTypeToken.Id.ToString()},
256          {"typeName", lastTypeToken.TypeName },
257          {"serializer", lastTypeToken.Serializer }});
258      } finally {
259        lastTypeToken = null;
260      }
261    }
262
263    /// <summary>
264    /// Formats the specified type cache.
265    /// </summary>
266    /// <param name="typeCache">The type cache.</param>
267    /// <returns>An enumerable of formatted type cache tags.</returns>
268    public IEnumerable<string> Format(List<TypeMapping> typeCache) {
269      yield return CreateNodeStart(XmlStringConstants.TYPECACHE);
270      foreach (var mapping in typeCache)
271        yield return CreateNode(
272          XmlStringConstants.TYPE,
273          mapping.GetDict());
274      yield return CreateNodeEnd(XmlStringConstants.TYPECACHE);
275    }
276
277    /// <summary>
278    /// Serialize an object into a file.
279    /// The XML configuration is obtained from the <c>ConfigurationService</c>.
280    /// The file is actually a ZIP file.
281    /// Compression level is set to 5 and needed assemblies are not included.
282    /// </summary>
283    /// <param name="o">The object.</param>
284    /// <param name="filename">The filename.</param>
285    public static void Serialize(object o, string filename) {
286      Serialize(o, filename, ConfigurationService.Instance.GetConfiguration(new XmlFormat()), false, CompressionLevel.Optimal);
287    }
288
289    /// <summary>
290    /// Serialize an object into a file.
291    /// The XML configuration is obtained from the <c>ConfigurationService</c>.
292    /// Needed assemblies are not included.
293    /// </summary>
294    /// <param name="o">The object.</param>
295    /// <param name="filename">The filename.</param>
296    /// <param name="compression">ZIP file compression level</param>
297    public static void Serialize(object o, string filename, CompressionLevel compression) {
298      Serialize(o, filename, ConfigurationService.Instance.GetConfiguration(new XmlFormat()), false, compression);
299    }
300
301    /// <summary>
302    /// Serializes the specified object into a file.
303    /// Needed assemblies are not included, ZIP compression level is set to 5.
304    /// </summary>
305    /// <param name="obj">The object.</param>
306    /// <param name="filename">The filename.</param>
307    /// <param name="config">The configuration.</param>
308    public static void Serialize(object obj, string filename, Configuration config) {
309      Serialize(obj, filename, config, false, CompressionLevel.Optimal);
310    }
311
312    private static void Serialize(object obj, Stream stream, Configuration config, bool includeAssemblies
313   , CompressionLevel compression) {
314      var serializer = new HeuristicLab.Persistence.Core.Serializer(obj, config);
315      Serialize(stream, includeAssemblies, compression, serializer);
316    }
317
318    private static void Serialize(Stream stream, bool includeAssemblies, CompressionLevel compression, HeuristicLab.Persistence.Core.Serializer serializer) {
319      try {
320        DateTime start = DateTime.Now;
321        serializer.InterleaveTypeInformation = false;
322        XmlGenerator generator = new XmlGenerator();
323        using (ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Create)) {
324          ZipArchiveEntry entry = zipArchive.CreateEntry("data.xml", compression);
325          using (StreamWriter writer = new StreamWriter(entry.Open())) {
326            foreach (ISerializationToken token in serializer) {
327              string line = generator.Format(token);
328              writer.Write(line);
329            }
330          }
331          entry = zipArchive.CreateEntry("typecache.xml", compression);
332          using (StreamWriter writer = new StreamWriter(entry.Open())) {
333            foreach (string line in generator.Format(serializer.TypeCache)) {
334              writer.Write(line);
335            }
336          }
337          if (includeAssemblies) {
338            foreach (string name in serializer.RequiredFiles) {
339              Uri uri = new Uri(name);
340              if (!uri.IsFile) {
341                Logger.Warn("cannot read non-local files");
342                continue;
343              }
344              entry = zipArchive.CreateEntry(Path.GetFileName(uri.PathAndQuery), compression);
345              using (BinaryWriter bw = new BinaryWriter(entry.Open())) {
346                using (FileStream reader = File.OpenRead(uri.PathAndQuery)) {
347                  byte[] buffer = new byte[1024 * 1024];
348                  while (true) {
349                    int bytesRead = reader.Read(buffer, 0, 1024 * 1024);
350                    if (bytesRead == 0)
351                      break;
352                    bw.Write(buffer, 0, bytesRead);
353                  }
354                }
355              }
356            }
357          }
358        }
359        Logger.Info(String.Format("serialization took {0} seconds with compression level {1}",
360          (DateTime.Now - start).TotalSeconds, compression));
361      } catch (Exception) {
362        Logger.Warn("Exception caught, no data has been serialized.");
363        throw;
364      }
365    }
366
367    /// <summary>
368    /// Serializes the specified object into a file.
369    /// </summary>
370    /// <param name="obj">The object.</param>
371    /// <param name="filename">The filename.</param>
372    /// <param name="config">The configuration.</param>
373    /// <param name="includeAssemblies">if set to <c>true</c> include needed assemblies.</param>
374    /// <param name="compression">The ZIP compression level.</param>
375    public static void Serialize(object obj, string filename, Configuration config, bool includeAssemblies, CompressionLevel compression) {
376      try {
377        string tempfile = Path.GetTempFileName();
378
379        using (FileStream stream = File.Create(tempfile)) {
380          Serialize(obj, stream, config, includeAssemblies, compression);
381        }
382
383        File.Copy(tempfile, filename, true);
384        File.Delete(tempfile);
385      } catch (Exception) {
386        Logger.Warn("Exception caught, no data has been written.");
387        throw;
388      }
389    }
390
391    /// <summary>
392    /// Serializes the specified object into a stream using the <see cref="XmlFormat"/>
393    /// obtained from the <see cref="ConfigurationService"/>.
394    /// </summary>
395    /// <param name="obj">The object.</param>
396    /// <param name="stream">The stream.</param>
397    /// <param name="compressionType">Type of compression, default is GZip.</param>
398    public static void Serialize(object obj, Stream stream, CompressionType compressionType = CompressionType.GZip) {
399      Serialize(obj, stream, ConfigurationService.Instance.GetConfiguration(new XmlFormat()), compressionType);
400    }
401
402
403    /// <summary>
404    /// Serializes the specified object into a stream.
405    /// </summary>
406    /// <param name="obj">The object.</param>
407    /// <param name="stream">The stream.</param>
408    /// <param name="config">The configuration.</param>
409    /// <param name="compressionType">Type of compression, default is GZip.</param>
410    public static void Serialize(object obj, Stream stream, Configuration config, CompressionType compressionType = CompressionType.GZip) {
411      Serialize(obj, stream, config, false, compressionType);
412    }
413
414    /// <summary>
415    /// Serializes the specified object into a stream.
416    /// </summary>
417    /// <param name="obj">The object.</param>
418    /// <param name="stream">The stream.</param>
419    /// <param name="config">The configuration.</param>
420    /// <param name="includeAssemblies">if set to <c>true</c> include need assemblies.</param>
421    /// <param name="compressionType">Type of compression, default is GZip.</param>
422    public static void Serialize(object obj, Stream stream, Configuration config, bool includeAssemblies,
423                                 CompressionType compressionType = CompressionType.GZip) {
424      try {
425        var serializer = new HeuristicLab.Persistence.Core.Serializer(obj, config);
426        if (compressionType == CompressionType.Zip) {
427          Serialize(obj, stream, config, includeAssemblies, CompressionLevel.Optimal);
428        } else {
429          Serialize(stream, serializer);
430        }
431      } catch (PersistenceException) {
432        throw;
433      } catch (Exception e) {
434        throw new PersistenceException("Unexpected exception during Serialization.", e);
435      }
436    }
437
438    /// <summary>
439    /// Serializes the specified object into a stream.
440    /// </summary>
441    /// <param name="obj">The object.</param>
442    /// <param name="stream">The stream.</param>
443    /// <param name="config">The configuration.</param>
444    /// <param name="includeAssemblies">if set to <c>true</c> include need assemblies.</param>
445    /// <param name="types">The list of all serialized types.</param>
446    /// <param name="compressionType">Type of compression, default is GZip.</param>
447    public static void Serialize(object obj, Stream stream, Configuration config, bool includeAssemblies, out IEnumerable<Type> types,
448                                 CompressionType compressionType = CompressionType.GZip) {
449      try {
450        var serializer = new HeuristicLab.Persistence.Core.Serializer(obj, config);
451        if (compressionType == CompressionType.Zip) {
452          Serialize(stream, includeAssemblies, CompressionLevel.Optimal, serializer);
453        } else {
454          Serialize(stream, serializer);
455        }
456        types = serializer.SerializedTypes;
457      } catch (PersistenceException) {
458        throw;
459      } catch (Exception e) {
460        throw new PersistenceException("Unexpected exception during Serialization.", e);
461      }
462    }
463
464    private static void Serialize(Stream stream, HeuristicLab.Persistence.Core.Serializer serializer) {
465      using (StreamWriter writer = new StreamWriter(new GZipStream(stream, CompressionMode.Compress))) {
466        serializer.InterleaveTypeInformation = true;
467        XmlGenerator generator = new XmlGenerator();
468        foreach (ISerializationToken token in serializer) {
469          string line = generator.Format(token);
470          writer.Write(line);
471        }
472        writer.Flush();
473      }
474    }
475  }
476}
Note: See TracBrowser for help on using the repository browser.