Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.Persistence/3.3/Default/Xml/XmlGenerator.cs @ 17334

Last change on this file since 17334 was 17181, checked in by swagner, 5 years ago

#2875: Merged r17180 from trunk to stable

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