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 }