1 // 2 // Copyright (c) 2013-2017 Carsten Sonne Larsen <cs@innolan.net> 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 using System; 23 using System.Collections; 24 using System.Linq; 25 using System.Reflection; 26 using System.Text; 27 using Ntp.Analyzer.Config.Attribute; 28 using Ntp.Analyzer.Config.Node; 29 using Ntp.Analyzer.Config.Table; 30 using Ntp.Analyzer.Interface; 31 32 namespace Ntp.Analyzer.Config.Compiler 33 { 34 public sealed class Decompiler 35 { 36 /// <summary> 37 /// Initializes a new instance of the <see cref="Decompiler" /> class. 38 /// </summary> Decompiler()39 public Decompiler() 40 { 41 IndentChar = ' '; 42 IndentSize = 8; 43 } 44 45 private StringBuilder builder; 46 47 /// <summary> 48 /// Gets or sets the Configuration to decompile. 49 /// </summary> 50 /// <value>The configuration.</value> 51 public Configuration Configuration { get; set; } 52 53 /// <summary> 54 /// Gets or sets a value indicating whether this <see cref="Decompiler" /> show default 55 /// values. 56 /// </summary> 57 /// <value><c>true</c> if show default values; otherwise, <c>false</c>.</value> 58 public bool ShowDefaultValues { get; set; } 59 60 /// <summary> 61 /// Gets or sets the indent char. 62 /// </summary> 63 /// <value>The indent char.</value> 64 public char IndentChar { get; set; } 65 66 /// <summary> 67 /// Gets or sets the size of the indent. 68 /// </summary> 69 /// <value>The size of the indent.</value> 70 public int IndentSize { get; set; } 71 72 /// <summary> 73 /// Execute the decompiler. 74 /// </summary> 75 /// <returns>Decompiled configuration as text.</returns> Execute()76 public string Execute() 77 { 78 builder = new StringBuilder(); 79 DecompileConfiguration(-1, null, Configuration); 80 return builder.ToString(); 81 } 82 AppendDateTimeKind(DateTimeKind value)83 private void AppendDateTimeKind(DateTimeKind value) 84 { 85 switch (value) 86 { 87 case DateTimeKind.Local: 88 builder.Append(Keyword.Find(Symbol.KeywordTimeStampLocal).Name); 89 break; 90 case DateTimeKind.Utc: 91 case DateTimeKind.Unspecified: 92 builder.Append(Keyword.Find(Symbol.KeywordTimeStampUtc).Name); 93 break; 94 default: 95 builder.Append(Keyword.Find(Symbol.KeywordTimeStampUtc).Name); 96 break; 97 } 98 } 99 100 /// <summary> 101 /// Appends indent to output. 102 /// </summary> 103 /// <param name="size">Indent size.</param> AppendIndent(int size)104 private void AppendIndent(int size) 105 { 106 builder.Append(string.Empty.PadLeft(size*IndentSize, IndentChar)); 107 } 108 109 /// <summary> 110 /// Appends the string to output. 111 /// </summary> 112 /// <param name="value">Value of node.</param> 113 /// <param name="layout">Special layout properties.</param> 114 /// <param name="indent">Indent count.</param> AppendString(string value, Layout layout, int indent)115 private void AppendString(string value, Layout layout, int indent) 116 { 117 switch (layout) 118 { 119 case Layout.Block: 120 builder.AppendLine("{ "); 121 var lines = value.Trim().Split( 122 new[] {Environment.NewLine}, 123 StringSplitOptions.None 124 ); 125 foreach (var line in lines) 126 { 127 AppendIndent(indent + 1); 128 builder.AppendLine(line.Trim()); 129 } 130 AppendIndent(indent); 131 builder.Append("}"); 132 break; 133 case Layout.Quoted: 134 builder.Append("\""); 135 builder.Append(value); 136 builder.Append("\""); 137 break; 138 case Layout.Normal: 139 if (value.Contains(" ") || value.Contains("\t")) 140 { 141 builder.Append("\""); 142 builder.Append(value); 143 builder.Append("\""); 144 } 145 else 146 { 147 builder.Append(value); 148 } 149 break; 150 default: 151 builder.Append(value); 152 break; 153 } 154 } 155 156 /// <summary> 157 /// Decompiles a complex node type (classes). 158 /// </summary> 159 /// <param name="indent">Indent count.</param> 160 /// <param name="node">Configuration node to decompile.</param> DecompileComplexType(int indent, object node)161 private void DecompileComplexType(int indent, object node) 162 { 163 var properties = node.GetType().GetProperties(). 164 OrderBy( 165 delegate(PropertyInfo info) 166 { 167 var attribute = System.Attribute.GetCustomAttribute(info, typeof(NtpaIndex)); 168 return ((NtpaIndex) attribute)?.Index ?? int.MaxValue; 169 }); 170 171 foreach (var property in properties) 172 { 173 if (DecompileSetting(indent, node, property)) 174 continue; 175 176 if (DecompileSettingCollection(indent, node, property)) 177 continue; 178 179 if (DecompileReferenceCollection(indent, node, property)) 180 continue; 181 182 DecompileReference(indent, node, property); 183 } 184 } 185 186 /// <summary> 187 /// Decompiles a configuration section. 188 /// </summary> 189 /// <param name="indent">Indent count</param> 190 /// <param name="typeName">Name of configuration section.</param> 191 /// <param name="node">Configuration node to decompile.</param> DecompileConfiguration(int indent, string typeName, IConfigurationNode node)192 private void DecompileConfiguration(int indent, string typeName, IConfigurationNode node) 193 { 194 if (node == null) 195 return; 196 197 // Swap builders 198 var temp = builder; 199 builder = new StringBuilder(); 200 201 DecompileComplexType(indent + 1, node); 202 203 // Swap builder back 204 var res = builder; 205 builder = temp; 206 207 // Skip empty nodes 208 if (res.ToString().Trim() == string.Empty) 209 return; 210 211 if (indent >= 0) 212 { 213 // Make a blank line 214 AppendIndent(indent); 215 builder.AppendLine(); 216 217 // Write header 218 AppendIndent(indent); 219 builder.Append(typeName); 220 221 if (node.ConfigName != null) 222 { 223 builder.Append(" "); 224 builder.Append(node.ConfigName); 225 } 226 227 builder.AppendLine(" {"); 228 } 229 230 builder.Append(res); 231 232 if (indent >= 0) 233 { 234 AppendIndent(indent); 235 builder.AppendLine("}"); 236 } 237 } 238 239 /// <summary> 240 /// Decompiles a reference property if applicable. 241 /// </summary> 242 /// <param name="indent">Indent count.</param> 243 /// <param name="node">Configuration node to decompile.</param> 244 /// <param name="property">Property to decompile.</param> DecompileReference(int indent, object node, PropertyInfo property)245 private void DecompileReference(int indent, object node, PropertyInfo property) 246 { 247 var reference = (NtpaReference) System.Attribute.GetCustomAttribute(property, typeof(NtpaReference)); 248 if (reference == null) 249 return; 250 251 var value = property.GetValue(node, null); 252 if (value == null) 253 return; 254 255 var keyword = Keyword.Find(reference.Keyword); 256 var configurationNode = value as IConfigurationNode; 257 if (configurationNode == null) 258 return; 259 260 var name = configurationNode.ConfigName; 261 DecompileSimpleType(indent, keyword.Name, name, null, Layout.Normal); 262 } 263 264 /// <summary> 265 /// Decompiles a property with a collection of references if applicable. 266 /// </summary> 267 /// <param name="indent">Indent count.</param> 268 /// <param name="node">Configuration node to decompile.</param> 269 /// <param name="property">Property to decompile.</param> 270 /// <returns><c>true</c> if property is a collection of references; otherwise, <c>false</c>.</returns> DecompileReferenceCollection(int indent, object node, PropertyInfo property)271 private bool DecompileReferenceCollection(int indent, object node, PropertyInfo property) 272 { 273 var attribute = System.Attribute.GetCustomAttribute(property, typeof(NtpaReferenceCollection)); 274 if (attribute == null) 275 return false; 276 277 var refCollection = (NtpaReferenceCollection) attribute; 278 279 var value = property.GetValue(node, null); 280 if (!(value is IEnumerable)) 281 return true; 282 283 var keyword = Keyword.Find(refCollection.Keyword); 284 foreach (var subObject in value as IEnumerable) 285 { 286 var configurationNode = subObject as IConfigurationNode; 287 if (configurationNode != null) 288 { 289 var name = configurationNode.ConfigName; 290 DecompileSimpleType(indent, keyword.Name, name, null, Layout.Normal); 291 } 292 } 293 294 return true; 295 } 296 297 /// <summary> 298 /// Decompiles a setting property if applicable. 299 /// </summary> 300 /// <param name="indent">Indent count.</param> 301 /// <param name="node">Configuration node to decompile.</param> 302 /// <param name="property">Property to decompile.</param> 303 /// <returns><c>true</c> if property is a setting; otherwise, <c>false</c>.</returns> DecompileSetting(int indent, object node, PropertyInfo property)304 private bool DecompileSetting(int indent, object node, PropertyInfo property) 305 { 306 var setting = (NtpaSetting) System.Attribute.GetCustomAttribute(property, typeof(NtpaSetting)); 307 if (setting == null) 308 return false; 309 310 var keyword = Keyword.Find(setting.Keyword); 311 var value = property.GetValue(node, null); 312 if (value == null) 313 return true; 314 315 if (value is IConfigurationNode) 316 { 317 DecompileConfiguration(indent, keyword.Name, value as IConfigurationNode); 318 } 319 else 320 { 321 DecompileSimpleType(indent, keyword.Name, value, setting.Value, setting.Layout); 322 } 323 324 return true; 325 } 326 327 /// <summary> 328 /// Decompiles a collection property if applicable. 329 /// </summary> 330 /// <param name="indent">Indent count.</param> 331 /// <param name="node">Configuration node to decompile.</param> 332 /// <param name="property">Property to decompile.</param> 333 /// <returns><c>true</c> if property is a collection; otherwise, <c>false</c>.</returns> DecompileSettingCollection(int indent, object node, PropertyInfo property)334 private bool DecompileSettingCollection(int indent, object node, PropertyInfo property) 335 { 336 var attribute = System.Attribute.GetCustomAttribute(property, typeof(NtpaSettingsCollection)); 337 if (attribute == null) 338 return false; 339 340 var collection = (NtpaSettingsCollection) attribute; 341 342 var value = property.GetValue(node, null); 343 if (value == null) 344 return true; 345 346 // Decompile the collection in flat style 347 if (collection.Flatten && value is IEnumerable) 348 { 349 foreach (var subObject in value as IEnumerable) 350 { 351 DecompileComplexType(indent, subObject); 352 } 353 return true; 354 } 355 356 if (!(value is IEnumerable)) 357 return true; 358 359 var raiseAdd = 0; 360 if (collection.Raise != Symbol.Undefined) 361 { 362 var raise = Keyword.Find(collection.Raise); 363 AppendIndent(indent); 364 builder.Append(raise.Name); 365 builder.AppendLine(" {"); 366 raiseAdd = 1; 367 } 368 369 var keyword = Keyword.Find(collection.Keyword); 370 foreach (var subObject in value as IEnumerable) 371 { 372 DecompileConfiguration(indent + raiseAdd, keyword.Name, subObject as IConfigurationNode); 373 } 374 375 if (collection.Raise != Symbol.Undefined) 376 { 377 AppendIndent(indent); 378 builder.AppendLine("}"); 379 } 380 381 return true; 382 } 383 384 /// <summary> 385 /// Decompiles a simple node type (integers, strings, etc.) 386 /// </summary> 387 /// <param name="indent">Indent count.</param> 388 /// <param name="name">The name of the node.</param> 389 /// <param name="value">The value of the node.</param> 390 /// <param name="defaultValue">Default value for this type.</param> 391 /// <param name="layout">Special layout properties.</param> DecompileSimpleType(int indent, string name, object value, object defaultValue, Layout layout)392 private void DecompileSimpleType(int indent, string name, object value, object defaultValue, Layout layout) 393 { 394 if (!ShowDefaultValues && value.Equals(defaultValue)) 395 return; 396 397 if (layout == Layout.Block) 398 { 399 AppendIndent(indent); 400 builder.AppendLine(); 401 } 402 403 AppendIndent(indent); 404 builder.Append(name); 405 builder.Append(" "); 406 407 if (value is bool) 408 { 409 builder.Append((bool) value ? "Yes" : "No"); 410 } 411 else if (value is string) 412 { 413 AppendString((string) value, layout, indent); 414 } 415 else if (value is DateTimeKind) 416 { 417 AppendDateTimeKind((DateTimeKind) value); 418 } 419 else if (value is Uri) 420 { 421 AppendString(((Uri) value).OriginalString, layout, indent); 422 } 423 else 424 { 425 builder.Append(value); 426 } 427 428 builder.AppendLine(); 429 } 430 } 431 }