2010-05-19 Jb Evain <jbevain@novell.com>
[mcs.git] / tools / mconfig / Mono.MonoConfig / FeatureNodeHandler.cs
blob918b4f645961c3bfce85810d36ff72b82486ad5e
1 //
2 // Authors:
3 // Marek Habersack (mhabersack@novell.com)
4 //
5 // (C) 2007 Novell, Inc
6 //
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 using System;
29 using System.Collections.Generic;
30 using System.IO;
31 using System.Text;
32 using System.Xml;
33 using System.Xml.XPath;
35 namespace Mono.MonoConfig
37 public class FeatureNodeHandler : IDocumentNodeHandler, IStorageConsumer, IFeatureGenerator
39 string name;
40 FeatureTarget target;
41 List <FeatureBlock> blocks;
42 List <FeatureAction> actionsBefore;
43 List <FeatureAction> actionsAfter;
44 Dictionary <string, FeatureNode> storage;
45 StringBuilder description;
47 public FeatureNodeHandler ()
49 blocks = new List <FeatureBlock> ();
50 actionsBefore = new List <FeatureAction> ();
51 actionsAfter = new List <FeatureAction> ();
52 description = new StringBuilder ();
55 public void ReadConfiguration (XPathNavigator nav)
57 name = Helpers.GetRequiredNonEmptyAttribute (nav, "name");
58 target = Helpers.ConvertEnum <FeatureTarget> (Helpers.GetRequiredNonEmptyAttribute (nav, "target"), "target");
60 XPathNodeIterator iter = nav.Select ("blocks/block[string-length (@name) > 0]");
61 while (iter.MoveNext ())
62 blocks.Add (new FeatureBlock (iter.Current, target));
64 iter = nav.Select ("description/text()");
65 string val;
66 while (iter.MoveNext ()) {
67 val = iter.Current.Value;
68 if (String.IsNullOrEmpty (val))
69 continue;
70 description.Append (val);
73 FeatureAction action;
74 iter = nav.Select ("actions/action[string-length (@type) > 0 and string-length (@when) > 0]");
75 while (iter.MoveNext ()) {
76 action = new FeatureAction (iter.Current);
77 switch (action.When) {
78 case ActionWhen.Before:
79 actionsBefore.Add (action);
80 break;
82 case ActionWhen.After:
83 actionsAfter.Add (action);
84 break;
86 default:
87 throw new ApplicationException (
88 String.Format ("Unknown 'when' attribute: {0}", action.When));
93 public void StoreConfiguration ()
95 AssertStorage ();
97 List <FeatureBlock> blocksClone = new List <FeatureBlock> (blocks.Count);
98 List <FeatureAction> abc = new List <FeatureAction> (actionsBefore.Count);
99 List <FeatureAction> aac = new List <FeatureAction> (actionsAfter.Count);
101 blocksClone.AddRange (blocks);
102 abc.AddRange (actionsBefore);
103 aac.AddRange (actionsAfter);
104 FeatureNode fn = new FeatureNode (blocksClone, description.ToString (), abc, aac);
106 if (storage.ContainsKey (name))
107 storage [name] = fn; // allow for silent override
108 else
109 storage.Add (name, fn);
111 blocks.Clear ();
112 actionsBefore.Clear ();
113 actionsAfter.Clear ();
114 description.Length = 0;
117 public void SetStorage (object storage)
119 this.storage = storage as Dictionary <string, FeatureNode>;
120 if (this.storage == null)
121 throw new ApplicationException ("Invalid storage type");
124 public ICollection <string> Features {
125 get {
126 AssertStorage ();
128 if (storage.Count == 0)
129 return null;
131 List <string> ret = new List <string> (storage.Count);
132 string desc;
134 foreach (KeyValuePair <string, FeatureNode> kvp in storage) {
135 desc = FormatFeatureDescription (kvp.Key, kvp.Value);
136 if (String.IsNullOrEmpty (desc))
137 continue;
138 ret.Add (desc);
141 return ret;
145 string FormatFeatureDescription (string name, FeatureNode fn)
147 if (fn == null)
148 return null;
150 List <FeatureBlock> lfb = fn.Blocks;
151 if (lfb == null || lfb.Count == 0)
152 return null;
154 StringBuilder ret = new StringBuilder ();
155 ret.AppendFormat ("{0} (Target: {1})", name, lfb [0].Target);
157 List <FeatureAction> al = fn.ActionsBefore;
158 if (al != null && al.Count > 0)
159 ret.AppendFormat ("; {0} actions before", al.Count);
161 al = fn.ActionsAfter;
162 if (al != null && al.Count > 0)
163 ret.AppendFormat ("; {0} actions after", al.Count);
165 ret.Append ("\n");
167 string desc = fn.Description;
168 if (String.IsNullOrEmpty (desc))
169 return ret.ToString ();
171 string indent = " ";
172 int maxLineWidth = Console.WindowWidth - indent.Length;
173 string[] dlines = desc.Trim ().Split ('\n');
174 string line;
176 foreach (string l in dlines) {
177 if (l.Length == 0) {
178 ret.Append ("\n");
179 continue;
182 line = l.Trim ();
183 if (line.Length > maxLineWidth)
184 ret.AppendFormat ("{0}\n", Helpers.BreakLongLine (line, indent, maxLineWidth));
185 else
186 ret.AppendFormat ("{0}{1}\n", indent, line);
189 ret.Append ("\n");
190 return ret.ToString ();
193 public bool HasFeature (string featureName)
195 AssertStorage ();
197 if (!storage.ContainsKey (featureName))
198 return false;
200 FeatureNode fn = storage [featureName];
201 if (fn == null)
202 return false;
204 List <FeatureBlock> blocks = fn.Blocks;
205 if (blocks == null || blocks.Count == 0)
206 return false;
208 return true;
211 public void AddFeature (string configFilePath, string featureName, FeatureTarget target,
212 IDefaultContainer[] defaults, IConfigBlockContainer[] configBlocks)
214 AssertStorage ();
216 FeatureNode fn;
218 if (!storage.ContainsKey (featureName) || (fn = storage [featureName]) == null)
219 throw new ApplicationException (String.Format ("Missing definition of feature '{0}'", featureName));
221 List <FeatureBlock> blocks = fn.Blocks;
222 if (blocks == null || blocks.Count == 0)
223 throw new ApplicationException (String.Format ("Definition of feature '{0}' is empty", featureName));
225 RunActions (fn.ActionsBefore);
226 XmlDocument doc = new XmlDocument ();
228 if (File.Exists (configFilePath))
229 doc.Load (configFilePath);
231 foreach (FeatureBlock block in blocks)
232 AddFeatureBlock (doc, block, target, defaults, configBlocks);
234 Helpers.SaveXml (doc, configFilePath);
235 RunActions (fn.ActionsAfter);
238 void RunActions (List <FeatureAction> actions)
240 if (actions == null || actions.Count == 0)
241 return;
243 foreach (FeatureAction action in actions) {
244 if (action == null)
245 continue;
246 action.Execute ();
250 void AddFeatureBlock (XmlDocument doc, FeatureBlock block, FeatureTarget target, IDefaultContainer[] defaults,
251 IConfigBlockContainer[] configBlocks)
253 if (target != FeatureTarget.Any && block.Target != target)
254 return;
256 ConfigBlockBlock configBlock = Helpers.FindConfigBlock (configBlocks, block.Name);
257 if (configBlock == null)
258 throw new ApplicationException (String.Format ("Config block '{0}' cannot be found", block.Name));
260 XmlNode attachPoint = null;
262 ProcessSections (doc, doc, "/", configBlock.Requires, defaults, configBlock.Name, ref attachPoint);
263 if (attachPoint == null)
264 attachPoint = FindDefaultAttachPoint (doc, configBlock.Requires);
265 if (attachPoint == null)
266 throw new ApplicationException (
267 String.Format ("Missing attachment point for block '{0}'", configBlock.Name));
269 XmlDocument contents = new XmlDocument ();
270 contents.LoadXml (String.Format ("<{0}>{1}</{0}>", Helpers.FakeRootName, configBlock.Contents));
271 AddFeatureRecursively (doc, attachPoint, contents.DocumentElement);
274 // TODO: handle comment and text nodes to avoid their duplication
275 void AddFeatureRecursively (XmlDocument doc, XmlNode attachPoint, XmlNode top)
277 bool topIsFake = top.Name == Helpers.FakeRootName;
278 XmlNode parent = null;
279 string xpath;
281 if (top.NodeType == XmlNodeType.Element) {
282 xpath = BuildFeaturePath (attachPoint, topIsFake ? null : top);
283 parent = DocumentHasFeatureFragment (doc, top, xpath);
286 if (!topIsFake && parent == null) {
287 parent = doc.ImportNode (top, false);
288 attachPoint.AppendChild (parent);
289 if (parent.NodeType == XmlNodeType.Comment)
290 return;
293 if (top.HasChildNodes)
294 foreach (XmlNode node in top.ChildNodes)
295 AddFeatureRecursively (doc, topIsFake ? attachPoint : parent, node);
298 XmlNode FindDefaultAttachPoint (XmlDocument doc, Section req)
300 List <Section> children = req.Children;
301 if (children == null || children.Count == 0)
302 return null;
304 StringBuilder sb = new StringBuilder ("/");
305 BuildPathToLastRequirement (sb, children);
307 return doc.SelectSingleNode (sb.ToString ());
310 void BuildPathToLastRequirement (StringBuilder sb, List <Section> sections)
312 Section last = sections [sections.Count - 1];
313 sb.AppendFormat ("/{0}", last.Name);
315 List <Section> children = last.Children;
316 if (children == null || children.Count == 0)
317 return;
319 BuildPathToLastRequirement (sb, children);
322 XmlNode DocumentHasFeatureFragment (XmlDocument doc, XmlNode top, string xpath)
324 if (top.NodeType == XmlNodeType.Comment)
325 return null;
327 return doc.SelectSingleNode (xpath);
330 string BuildFeaturePath (XmlNode parent, XmlNode child)
332 if (parent == null)
333 return "/";
335 List <string> path = new List <string> ();
337 XmlNode cur = parent, last = null;
338 while (cur != null && cur.NodeType != XmlNodeType.Document) {
339 if (cur.NodeType == XmlNodeType.Element && cur.Name != Helpers.FakeRootName)
340 path.Insert (0, cur.Name);
341 last = cur;
342 cur = cur.ParentNode;
345 string attributes = null;
346 if (child != null && last.Name != child.Name) {
347 if (child.NodeType == XmlNodeType.Element)
348 path.Add (child.Name);
350 attributes = BuildXPathFromAttributes (child);
351 } else if (last != null)
352 attributes = BuildXPathFromAttributes (last);
354 path [path.Count - 1] += attributes;
355 path.Insert (0, "/");
357 return String.Join ("/", path.ToArray ());
360 string BuildXPathFromAttributes (XmlNode node)
362 XmlAttributeCollection attrs = node.Attributes;
363 StringBuilder sb = new StringBuilder ();
364 string and = String.Empty;
365 bool first = true;
367 foreach (XmlAttribute attr in attrs) {
368 sb.AppendFormat ("{0}@{1}=\"{2}\"",
369 and,
370 attr.Name,
371 attr.Value);
372 if (first) {
373 first = false;
374 and = " and ";
378 if (sb.Length == 0)
379 return String.Empty;
381 sb.Insert (0, "[");
382 sb.Append ("]");
384 return sb.ToString ();
387 void ProcessSections (XmlDocument doc, XmlNode parent, string topPath, Section top, IDefaultContainer[] defaults,
388 string blockName, ref XmlNode attachPoint)
390 List <Section> topChildren, children;
391 if (top == null || (topChildren = top.Children) == null)
392 return;
394 XmlNode node;
395 string curPath;
397 foreach (Section s in topChildren) {
398 curPath = String.Format ("{0}/{1}", topPath, s.Name);
400 node = FindNodeOrAddDefault (doc, s.DefaultBlockName, curPath, defaults);
401 if (node != null && s.AttachPoint) {
402 if (attachPoint != null)
403 throw new ApplicationException (
404 String.Format ("Config block '{0}' has more than one attachment point",
405 blockName));
406 attachPoint = node;
408 parent.AppendChild (node);
410 if ((children = s.Children) != null && children.Count > 0)
411 ProcessSections (doc, node, curPath, s, defaults, blockName, ref attachPoint);
414 return;
417 XmlNode FindNodeOrAddDefault (XmlDocument doc, string nodeName, string nodePath, IDefaultContainer[] defaults)
419 XmlNode ret = doc.SelectSingleNode (nodePath);
421 if (ret != null)
422 return ret;
424 XmlDocument defDoc = Helpers.FindDefault (defaults, nodeName, FeatureTarget.Any);
425 if (defDoc == null)
426 throw new ApplicationException (
427 String.Format ("Document doesn't contain node '{0}' and no default can be found",
428 nodePath));
430 return doc.ImportNode (defDoc.DocumentElement.FirstChild, true);
433 void AssertStorage ()
435 if (storage == null)
436 throw new ApplicationException ("No storage attached");