3 // Marek Habersack (mhabersack@novell.com)
5 // (C) 2007 Novell, Inc
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
29 using System
.Collections
.Generic
;
33 using System
.Xml
.XPath
;
35 namespace Mono
.MonoConfig
37 public class FeatureNodeHandler
: IDocumentNodeHandler
, IStorageConsumer
, IFeatureGenerator
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()");
66 while (iter
.MoveNext ()) {
67 val
= iter
.Current
.Value
;
68 if (String
.IsNullOrEmpty (val
))
70 description
.Append (val
);
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
);
82 case ActionWhen
.After
:
83 actionsAfter
.Add (action
);
87 throw new ApplicationException (
88 String
.Format ("Unknown 'when' attribute: {0}", action
.When
));
93 public void StoreConfiguration ()
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
109 storage
.Add (name
, fn
);
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
{
128 if (storage
.Count
== 0)
131 List
<string> ret
= new List
<string> (storage
.Count
);
134 foreach (KeyValuePair
<string, FeatureNode
> kvp
in storage
) {
135 desc
= FormatFeatureDescription (kvp
.Key
, kvp
.Value
);
136 if (String
.IsNullOrEmpty (desc
))
145 string FormatFeatureDescription (string name
, FeatureNode fn
)
150 List
<FeatureBlock
> lfb
= fn
.Blocks
;
151 if (lfb
== null || lfb
.Count
== 0)
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
);
167 string desc
= fn
.Description
;
168 if (String
.IsNullOrEmpty (desc
))
169 return ret
.ToString ();
172 int maxLineWidth
= Console
.WindowWidth
- indent
.Length
;
173 string[] dlines
= desc
.Trim ().Split ('\n');
176 foreach (string l
in dlines
) {
183 if (line
.Length
> maxLineWidth
)
184 ret
.AppendFormat ("{0}\n", Helpers
.BreakLongLine (line
, indent
, maxLineWidth
));
186 ret
.AppendFormat ("{0}{1}\n", indent
, line
);
190 return ret
.ToString ();
193 public bool HasFeature (string featureName
)
197 if (!storage
.ContainsKey (featureName
))
200 FeatureNode fn
= storage
[featureName
];
204 List
<FeatureBlock
> blocks
= fn
.Blocks
;
205 if (blocks
== null || blocks
.Count
== 0)
211 public void AddFeature (string configFilePath
, string featureName
, FeatureTarget target
,
212 IDefaultContainer
[] defaults
, IConfigBlockContainer
[] configBlocks
)
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)
243 foreach (FeatureAction action
in actions
) {
250 void AddFeatureBlock (XmlDocument doc
, FeatureBlock block
, FeatureTarget target
, IDefaultContainer
[] defaults
,
251 IConfigBlockContainer
[] configBlocks
)
253 if (target
!= FeatureTarget
.Any
&& block
.Target
!= target
)
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;
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
)
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)
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)
319 BuildPathToLastRequirement (sb
, children
);
322 XmlNode
DocumentHasFeatureFragment (XmlDocument doc
, XmlNode top
, string xpath
)
324 if (top
.NodeType
== XmlNodeType
.Comment
)
327 return doc
.SelectSingleNode (xpath
);
330 string BuildFeaturePath (XmlNode parent
, XmlNode child
)
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
);
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
;
367 foreach (XmlAttribute attr
in attrs
) {
368 sb
.AppendFormat ("{0}@{1}=\"{2}\"",
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)
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",
408 parent
.AppendChild (node
);
410 if ((children
= s
.Children
) != null && children
.Count
> 0)
411 ProcessSections (doc
, node
, curPath
, s
, defaults
, blockName
, ref attachPoint
);
417 XmlNode
FindNodeOrAddDefault (XmlDocument doc
, string nodeName
, string nodePath
, IDefaultContainer
[] defaults
)
419 XmlNode ret
= doc
.SelectSingleNode (nodePath
);
424 XmlDocument defDoc
= Helpers
.FindDefault (defaults
, nodeName
, FeatureTarget
.Any
);
426 throw new ApplicationException (
427 String
.Format ("Document doesn't contain node '{0}' and no default can be found",
430 return doc
.ImportNode (defDoc
.DocumentElement
.FirstChild
, true);
433 void AssertStorage ()
436 throw new ApplicationException ("No storage attached");