source: trunk/sources/HeuristicLab.Persistence/3.3/Default/Xml/XmlGenerator.cs @ 11171

Last change on this file since 11171 was 11171, checked in by ascheibe, 8 years ago

#2115 merged r11170 (copyright update) into trunk

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