#region License Information /* HeuristicLab * Copyright (C) 2002-2018 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Text; using HeuristicLab.Persistence.Core; using HeuristicLab.Persistence.Core.Tokens; using HeuristicLab.Persistence.Interfaces; using HeuristicLab.Tracing; namespace HeuristicLab.Persistence.Default.Xml { /// /// Main entry point of persistence to XML. Use the static methods to serialize /// to a file or to a stream. /// public class XmlGenerator : GeneratorBase { protected int depth; protected int Depth { get { return depth; } set { depth = value; prefix = new string(' ', depth * 2); } } protected string prefix; /// /// Initializes a new instance of the class. /// public XmlGenerator() { Depth = 0; } protected enum NodeType { Start, End, Inline } ; protected static void AddXmlTagContent(StringBuilder sb, string name, Dictionary attributes) { sb.Append(name); foreach (var attribute in attributes) { if (attribute.Value != null && !string.IsNullOrEmpty(attribute.Value.ToString())) { sb.Append(' '); sb.Append(attribute.Key); sb.Append("=\""); sb.Append(attribute.Value); sb.Append('"'); } } } protected static int AttributeLength(Dictionary attributes) { return attributes .Where(kvp => !string.IsNullOrEmpty(kvp.Key) && !string.IsNullOrEmpty(kvp.Value)) .Select(kvp => kvp.Key.Length + kvp.Value.Length + 4).Sum(); } protected static void AddXmlStartTag(StringBuilder sb, string name, Dictionary attributes) { sb.Append('<'); AddXmlTagContent(sb, name, attributes); sb.Append('>'); } protected static void AddXmlInlineTag(StringBuilder sb, string name, Dictionary attributes) { sb.Append('<'); AddXmlTagContent(sb, name, attributes); sb.Append("/>"); } protected static void AddXmlEndTag(StringBuilder sb, string name) { sb.Append(""); } protected string CreateNodeStart(string name, Dictionary attributes) { StringBuilder sb = new StringBuilder(prefix.Length + name.Length + 4 + AttributeLength(attributes)); sb.Append(prefix); Depth += 1; AddXmlStartTag(sb, name, attributes); sb.Append("\r\n"); return sb.ToString(); } private static Dictionary emptyDict = new Dictionary(); protected string CreateNodeStart(string name) { return CreateNodeStart(name, emptyDict); } protected string CreateNodeEnd(string name) { Depth -= 1; StringBuilder sb = new StringBuilder(prefix.Length + name.Length + 5); sb.Append(prefix); AddXmlEndTag(sb, name); sb.Append("\r\n"); return sb.ToString(); } protected string CreateNode(string name, Dictionary attributes) { StringBuilder sb = new StringBuilder(prefix.Length + name.Length + 5 + AttributeLength(attributes)); sb.Append(prefix); AddXmlInlineTag(sb, name, attributes); sb.Append("\r\n"); return sb.ToString(); } protected string CreateNode(string name, Dictionary attributes, string content) { StringBuilder sb = new StringBuilder( prefix.Length + name.Length + AttributeLength(attributes) + 2 + content.Length + name.Length + 5); sb.Append(prefix); AddXmlStartTag(sb, name, attributes); sb.Append(content); sb.Append("\r\n"); return sb.ToString(); } /// /// Formats the specified begin token. /// /// The begin token. /// The token in serialized form. protected override string Format(BeginToken beginToken) { var dict = new Dictionary { {"name", beginToken.Name}, {"typeId", beginToken.TypeId.ToString()}, {"id", beginToken.Id.ToString()}}; AddTypeInfo(beginToken.TypeId, dict); return CreateNodeStart(XmlStringConstants.COMPOSITE, dict); } protected void AddTypeInfo(int typeId, Dictionary dict) { if (lastTypeToken != null) { if (typeId == lastTypeToken.Id) { dict.Add("typeName", lastTypeToken.TypeName); dict.Add("serializer", lastTypeToken.Serializer); lastTypeToken = null; } else { FlushTypeToken(); } } } /// /// Formats the specified end token. /// /// The end token. /// The token in serialized form. protected override string Format(EndToken endToken) { return CreateNodeEnd(XmlStringConstants.COMPOSITE); } /// /// Formats the specified data token. /// /// The data token. /// The token in serialized form. protected override string Format(PrimitiveToken dataToken) { var dict = new Dictionary { {"typeId", dataToken.TypeId.ToString()}, {"name", dataToken.Name}, {"id", dataToken.Id.ToString()}}; AddTypeInfo(dataToken.TypeId, dict); return CreateNode(XmlStringConstants.PRIMITIVE, dict, ((XmlString)dataToken.SerialData).Data); } /// /// Formats the specified ref token. /// /// The ref token. /// The token in serialized form. protected override string Format(ReferenceToken refToken) { return CreateNode(XmlStringConstants.REFERENCE, new Dictionary { {"ref", refToken.Id.ToString()}, {"name", refToken.Name}}); } /// /// Formats the specified null ref token. /// /// The null ref token. /// The token in serialized form. protected override string Format(NullReferenceToken nullRefToken) { return CreateNode(XmlStringConstants.NULL, new Dictionary{ {"name", nullRefToken.Name}}); } /// /// Formats the specified meta info begin token. /// /// The meta info begin token. /// The token in serialized form. protected override string Format(MetaInfoBeginToken metaInfoBeginToken) { return CreateNodeStart(XmlStringConstants.METAINFO); } /// /// Formats the specified meta info end token. /// /// The meta info end token. /// The token in serialized form. protected override string Format(MetaInfoEndToken metaInfoEndToken) { return CreateNodeEnd(XmlStringConstants.METAINFO); } protected TypeToken lastTypeToken; /// /// Formats the specified token. /// /// The token. /// The token in serialized form. protected override string Format(TypeToken token) { lastTypeToken = token; return ""; } protected string FlushTypeToken() { if (lastTypeToken == null) return ""; try { return CreateNode(XmlStringConstants.TYPE, new Dictionary { {"id", lastTypeToken.Id.ToString()}, {"typeName", lastTypeToken.TypeName }, {"serializer", lastTypeToken.Serializer }}); } finally { lastTypeToken = null; } } /// /// Formats the specified type cache. /// /// The type cache. /// An enumerable of formatted type cache tags. public IEnumerable Format(List typeCache) { yield return CreateNodeStart(XmlStringConstants.TYPECACHE); foreach (var mapping in typeCache) yield return CreateNode( XmlStringConstants.TYPE, mapping.GetDict()); yield return CreateNodeEnd(XmlStringConstants.TYPECACHE); } /// /// Serialize an object into a file. /// The XML configuration is obtained from the ConfigurationService. /// The file is actually a ZIP file. /// Compression level is set to 5 and needed assemblies are not included. /// /// The object. /// The filename. public static void Serialize(object o, string filename) { Serialize(o, filename, ConfigurationService.Instance.GetConfiguration(new XmlFormat()), false, CompressionLevel.Optimal); } /// /// Serialize an object into a file. /// The XML configuration is obtained from the ConfigurationService. /// Needed assemblies are not included. /// /// The object. /// The filename. /// ZIP file compression level public static void Serialize(object o, string filename, CompressionLevel compression) { Serialize(o, filename, ConfigurationService.Instance.GetConfiguration(new XmlFormat()), false, compression); } /// /// Serializes the specified object into a file. /// Needed assemblies are not included, ZIP compression level is set to 5. /// /// The object. /// The filename. /// The configuration. public static void Serialize(object obj, string filename, Configuration config) { Serialize(obj, filename, config, false, CompressionLevel.Optimal); } private static void Serialize(object obj, Stream stream, Configuration config, bool includeAssemblies , CompressionLevel compression) { Serializer serializer = new Serializer(obj, config); Serialize(stream, includeAssemblies, compression, serializer); } private static void Serialize(Stream stream, bool includeAssemblies, CompressionLevel compression, Serializer serializer) { try { DateTime start = DateTime.Now; serializer.InterleaveTypeInformation = false; XmlGenerator generator = new XmlGenerator(); using (ZipArchive zipArchive = new ZipArchive(stream, ZipArchiveMode.Create)) { ZipArchiveEntry entry = zipArchive.CreateEntry("data.xml", compression); using (StreamWriter writer = new StreamWriter(entry.Open())) { foreach (ISerializationToken token in serializer) { string line = generator.Format(token); writer.Write(line); } } entry = zipArchive.CreateEntry("typecache.xml", compression); using (StreamWriter writer = new StreamWriter(entry.Open())) { foreach (string line in generator.Format(serializer.TypeCache)) { writer.Write(line); } } if (includeAssemblies) { foreach (string name in serializer.RequiredFiles) { Uri uri = new Uri(name); if (!uri.IsFile) { Logger.Warn("cannot read non-local files"); continue; } entry = zipArchive.CreateEntry(Path.GetFileName(uri.PathAndQuery), compression); using (BinaryWriter bw = new BinaryWriter(entry.Open())) { using (FileStream reader = File.OpenRead(uri.PathAndQuery)) { byte[] buffer = new byte[1024 * 1024]; while (true) { int bytesRead = reader.Read(buffer, 0, 1024 * 1024); if (bytesRead == 0) break; bw.Write(buffer, 0, bytesRead); } } } } } } Logger.Info(String.Format("serialization took {0} seconds with compression level {1}", (DateTime.Now - start).TotalSeconds, compression)); } catch (Exception) { Logger.Warn("Exception caught, no data has been serialized."); throw; } } /// /// Serializes the specified object into a file. /// /// The object. /// The filename. /// The configuration. /// if set to true include needed assemblies. /// The ZIP compression level. public static void Serialize(object obj, string filename, Configuration config, bool includeAssemblies, CompressionLevel compression) { try { string tempfile = Path.GetTempFileName(); using (FileStream stream = File.Create(tempfile)) { Serialize(obj, stream, config, includeAssemblies, compression); } File.Copy(tempfile, filename, true); File.Delete(tempfile); } catch (Exception) { Logger.Warn("Exception caught, no data has been written."); throw; } } /// /// Serializes the specified object into a stream using the /// obtained from the . /// /// The object. /// The stream. /// Type of compression, default is GZip. public static void Serialize(object obj, Stream stream, CompressionType compressionType = CompressionType.GZip) { Serialize(obj, stream, ConfigurationService.Instance.GetConfiguration(new XmlFormat()), compressionType); } /// /// Serializes the specified object into a stream. /// /// The object. /// The stream. /// The configuration. /// Type of compression, default is GZip. public static void Serialize(object obj, Stream stream, Configuration config, CompressionType compressionType = CompressionType.GZip) { Serialize(obj, stream, config, false, compressionType); } /// /// Serializes the specified object into a stream. /// /// The object. /// The stream. /// The configuration. /// if set to true include need assemblies. /// Type of compression, default is GZip. public static void Serialize(object obj, Stream stream, Configuration config, bool includeAssemblies, CompressionType compressionType = CompressionType.GZip) { try { Serializer serializer = new Serializer(obj, config); if (compressionType == CompressionType.Zip) { Serialize(obj, stream, config, includeAssemblies, CompressionLevel.Optimal); } else { Serialize(stream, serializer); } } catch (PersistenceException) { throw; } catch (Exception e) { throw new PersistenceException("Unexpected exception during Serialization.", e); } } /// /// Serializes the specified object into a stream. /// /// The object. /// The stream. /// The configuration. /// if set to true include need assemblies. /// The list of all serialized types. /// Type of compression, default is GZip. public static void Serialize(object obj, Stream stream, Configuration config, bool includeAssemblies, out IEnumerable types, CompressionType compressionType = CompressionType.GZip) { try { Serializer serializer = new Serializer(obj, config); if (compressionType == CompressionType.Zip) { Serialize(stream, includeAssemblies, CompressionLevel.Optimal, serializer); } else { Serialize(stream, serializer); } types = serializer.SerializedTypes; } catch (PersistenceException) { throw; } catch (Exception e) { throw new PersistenceException("Unexpected exception during Serialization.", e); } } private static void Serialize(Stream stream, Serializer serializer) { using (StreamWriter writer = new StreamWriter(new GZipStream(stream, CompressionMode.Compress))) { serializer.InterleaveTypeInformation = true; XmlGenerator generator = new XmlGenerator(); foreach (ISerializationToken token in serializer) { string line = generator.Format(token); writer.Write(line); } writer.Flush(); } } } }