2002-10-06 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System / System.Configuration / ConfigurationSettings.cs
1 //
2 // System.Configuration.ConfigurationSettings.cs
3 //
4 // Author:
5 //   Christopher Podurgiel (cpodurgiel@msn.com)
6 //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // C) Christopher Podurgiel
9 // (c) 2002 Ximian, Inc. (http://www.ximian.com)
10 //
11
12 using System;
13 using System.Collections;
14 using System.Collections.Specialized;
15 using System.IO;
16 using System.Reflection;
17 using System.Xml;
18 using System.Xml.XPath;
19
20 namespace System.Configuration
21 {
22         public sealed class ConfigurationSettings
23         {
24                 static IConfigurationSystem config;
25                         
26                 private ConfigurationSettings ()
27                 {
28                 }
29
30                 public static object GetConfig (string sectionName)
31                 {
32                         if (config == null)
33                                 config = DefaultConfig.GetInstance ();
34
35                         return config.GetConfig (sectionName);
36                 }
37
38                 public static NameValueCollection AppSettings
39                 {
40                         get {
41                                 object appSettings = GetConfig ("appSettings");
42                                 if (appSettings == null)
43                                         appSettings = new NameValueCollection ();
44
45                                 return (NameValueCollection) appSettings;
46                         }
47                 }
48
49         }
50
51         //
52         // class DefaultConfig: read configuration from machine.config file and application
53         // config file if available.
54         //
55         class DefaultConfig : IConfigurationSystem
56         {
57                 static string creatingInstance = "137213797382-asad";
58                 static string buildingData = "1797382-ladgasjkdg";
59                 static DefaultConfig instance;
60                 ConfigurationData config;
61
62                 private DefaultConfig ()
63                 {
64                 }
65
66                 public static DefaultConfig GetInstance ()
67                 {
68                         if (instance == null) {
69                                 lock (creatingInstance) {
70                                         if (instance == null) {
71                                                 instance = new DefaultConfig ();
72                                                 instance.Init ();
73                                         }
74                                         
75                                 }
76                         }
77
78                         return instance;
79                 }
80
81                 public object GetConfig (string sectionName)
82                 {
83                         return config.GetConfig (sectionName);
84                 }
85
86                 public void Init ()
87                 {
88                         if (config == null)
89                                 lock (buildingData) {
90                                         if (config != null)
91                                                 return;
92
93                                         ConfigurationData data = new ConfigurationData ();
94                                         if (data.Load (GetMachineConfigPath ())) {
95                                                 ConfigurationData appData = new ConfigurationData (data);
96                                                 appData.Load (GetAppConfigPath ());
97                                                 config = appData;
98                                         }
99                                 }
100                 }
101
102                 private static string GetMachineConfigPath ()
103                 {
104                         string location = typeof (string).Assembly.Location;
105                         // Workaround for bug #31730
106                         int index = location.IndexOf ("install");
107                         location = Path.Combine (location.Substring (0, index + 7), "lib");
108                         // 
109                         return Path.Combine (location, "machine.config");
110                 }
111
112                 private static string GetAppConfigPath ()
113                 {
114                         AppDomainSetup currentInfo = AppDomain.CurrentDomain.SetupInformation;
115
116                         string appBase = currentInfo.ApplicationBase;
117                         string configFile = currentInfo.ConfigurationFile;
118                         // FIXME: need to check out default domain configuration file name
119                         if (configFile == null || configFile.Length == 0)
120                                 return null;
121
122                         return Path.Combine (appBase, configFile);
123                 }
124         }
125
126         class ConfigurationData
127         {
128                 ConfigurationData parent;
129                 Hashtable factories;
130                 string fileName;
131                 object removedMark = new object ();
132                 object groupMark = new object ();
133
134                 public ConfigurationData () : this (null)
135                 {
136                 }
137
138                 public ConfigurationData (ConfigurationData parent)
139                 {
140                         this.parent = parent;
141                         factories = new Hashtable ();
142                 }
143
144                 public bool Load (string fileName)
145                 {
146                         if (fileName == null)
147                                 return false;
148
149                         this.fileName = fileName;
150                         XmlTextReader reader = null;
151
152                         try {
153                                 reader = new XmlTextReader (fileName);
154                                 InitRead (reader);
155                                 MoveToNextElement (reader);
156                                 ReadSections (reader, null);
157                         } finally {
158                                 if (reader == null)
159                                         reader.Close();
160                         }
161
162                         return true;
163                 }
164
165                 IConfigurationSectionHandler GetSectionHandler (string sectionName)
166                 {
167                         if (!factories.Contains (sectionName)) {
168                                 if (parent == null)
169                                         return null;
170
171                                 return parent.GetConfig (sectionName) as IConfigurationSectionHandler;
172                         }
173                         
174                         object o = factories [sectionName];
175                         if (o == removedMark)
176                                 return null;
177
178                         if (o is IConfigurationSectionHandler)
179                                 return (IConfigurationSectionHandler) o;
180
181                         Type iconfig = typeof (IConfigurationSectionHandler);
182                         string [] typeInfo = ((string) o).Split (',');
183                         Type t;
184                         
185                         // Hack. Type.GetType should be enough
186                         if (typeInfo.Length > 1) {
187                                 Assembly ass = Assembly.Load (typeInfo [1].Trim ());
188                                 t = ass.GetType (typeInfo [0].Trim ());
189                         } else {
190                                 t = Type.GetType (typeInfo [0]);
191                         }
192                         
193                         if (t == null)
194                                 throw new ConfigurationException ("Cannot get Type for " + o);
195
196                         if (!iconfig.IsAssignableFrom (t))
197                                 throw new ConfigurationException (sectionName + " does not implement " + iconfig);
198                         
199                         o = Activator.CreateInstance (t, true);
200                         if (o == null)
201                                 throw new ConfigurationException ("Cannot get instance for " + t);
202                         
203                         return (IConfigurationSectionHandler) o;
204                 }
205
206                 //TODO: Should use XPath when it works properly for this.
207                 XmlDocument GetDocumentForSection (string sectionName)
208                 {
209                         ConfigXmlDocument doc = new ConfigXmlDocument ();
210                         XmlTextReader reader = new XmlTextReader (fileName);
211                         InitRead (reader);
212                         string [] sectionPath = sectionName.Split ('/');
213                         int i = 0;
214                         if (!reader.EOF) {
215                                 reader.Skip ();
216                                 while (!reader.EOF) {
217                                         if (reader.NodeType == XmlNodeType.Element &&
218                                             reader.Name == sectionPath [i]) {
219                                                 if (++i == sectionPath.Length) {
220                                                         doc.LoadSingleElement (fileName, reader);
221                                                         break;
222                                                 }
223                                                 MoveToNextElement (reader);
224                                                 continue;
225                                         }
226                                         reader.Skip ();
227                                         if (reader.NodeType != XmlNodeType.Element)
228                                                 MoveToNextElement (reader);
229                                 }
230                         }
231
232                         reader.Close ();
233                         return doc;
234                 }
235                 
236                 public object GetConfig (string sectionName)
237                 {
238                         IConfigurationSectionHandler handler = GetSectionHandler (sectionName);
239                         if (handler == null)
240                                 return null;
241
242                         factories [sectionName] = handler;
243
244                         XmlDocument doc = GetDocumentForSection (sectionName);
245                         if (doc == null)
246                                 throw new ConfigurationException ("Section not found: " + sectionName);
247
248                         return handler.Create (null, null, doc);
249                 }
250
251                 private object LookForFactory (string key)
252                 {
253                         object o = factories [key];
254                         if (o != null)
255                                 return o;
256
257                         if (parent != null)
258                                 return parent.LookForFactory (key);
259
260                         return null;
261                 }
262                 
263                 private void InitRead (XmlTextReader reader)
264                 {
265                         reader.MoveToContent ();
266                         if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
267                                 ThrowException ("Configuration file does not have a valid root element", reader);
268
269                         if (reader.HasAttributes)
270                                 ThrowException ("Unrecognized attribute in root element", reader);
271
272                         MoveToNextElement (reader);
273                         if (reader.Depth == 1 && reader.Name == "configSections") {
274                                 if (reader.HasAttributes)
275                                         ThrowException ("Unrecognized attribute in configSections element.",
276                                                         reader);
277                         }
278                 }
279
280                 private void MoveToNextElement (XmlTextReader reader)
281                 {
282                         while (reader.Read ()) {
283                                 XmlNodeType ntype = reader.NodeType;
284                                 if (ntype == XmlNodeType.Element)
285                                         return;
286
287                                 if (ntype != XmlNodeType.Whitespace &&
288                                     ntype != XmlNodeType.Comment &&
289                                     ntype != XmlNodeType.SignificantWhitespace &&
290                                     ntype != XmlNodeType.EndElement)
291                                         ThrowException ("Unrecognized element", reader);
292                         }
293                 }
294
295                 private void ReadSection (XmlTextReader reader, string sectionName)
296                 {
297                         string attName;
298                         string nameValue = null;
299                         string typeValue = null;
300
301                         while (reader.MoveToNextAttribute ()) {
302                                 attName = reader.Name;
303                                 if (attName == null)
304                                         continue;
305
306                                 if (attName == "allowLocation" || attName == "allowDefinition")
307                                         continue;
308
309                                 if (attName == "type")  {
310                                         if (typeValue != null)
311                                                 ThrowException ("Duplicated type attribute.", reader);
312                                         typeValue = reader.Value;
313                                         continue;
314                                 }
315                                 
316                                 if (attName == "name")  {
317                                         if (nameValue != null)
318                                                 ThrowException ("Duplicated name attribute.", reader);
319                                         nameValue = reader.Value;
320                                         continue;
321                                 }
322
323                                 ThrowException ("Unrecognized attribute.", reader);
324                         }
325
326                         if (nameValue == null || typeValue == null)
327                                 ThrowException ("Required attribute missing", reader);
328
329                         if (sectionName != null)
330                                 nameValue = sectionName + '/' + nameValue;
331
332                         reader.MoveToElement();
333                         object o = LookForFactory (nameValue);
334                         if (o != null && o != removedMark)
335                                 ThrowException ("Already have a factory for " + nameValue, reader);
336
337                         factories [nameValue] = typeValue;
338                         MoveToNextElement (reader);
339                 }
340
341                 private void ReadRemoveSection (XmlTextReader reader, string sectionName)
342                 {
343                         if (!reader.MoveToNextAttribute () || reader.Name != "name")
344                                 ThrowException ("Unrecognized attribute.", reader);
345
346                         string removeValue = reader.Value;
347                         if (removeValue == null || removeValue.Length == 0)
348                                 ThrowException ("Empty name to remove", reader);
349
350                         reader.MoveToElement ();
351
352                         if (sectionName != null)
353                                 removeValue = sectionName + '/' + removeValue;
354
355                         object o = LookForFactory (removeValue);
356                         if (o != null && o != removedMark)
357                                 ThrowException ("No factory for " + removeValue, reader);
358
359                         factories [removeValue] = removedMark;
360                         MoveToNextElement (reader);
361                 }
362
363                 private void ReadSectionGroup (XmlTextReader reader, string configSection)
364                 {
365                         if (!reader.MoveToNextAttribute ())
366                                 ThrowException ("sectionGroup must have a 'name' attribute.", reader);
367
368                         if (reader.Name != "name")
369                                 ThrowException ("Unrecognized attribute.", reader);
370
371                         if (reader.MoveToNextAttribute ())
372                                 ThrowException ("Unrecognized attribute.", reader);
373
374                         string value = reader.Value;
375                         if (configSection != null)
376                                 value = configSection + '/' + value;
377
378                         object o = LookForFactory (value);
379                         if (o != null && o != removedMark)
380                                 ThrowException ("Already have a factory for " + value, reader);
381
382                         factories [value] = groupMark;
383                         MoveToNextElement (reader);
384                         ReadSections (reader, value);
385                 }
386
387                 private void ReadSections (XmlTextReader reader, string configSection)
388                 {
389                         int depth = reader.Depth;
390                         while (reader.Depth == depth) {
391                                 string name = reader.Name;
392                                 if (reader.Name == null)
393                                         continue;
394                                         
395                                 if (name == "section") {
396                                         ReadSection (reader, configSection);
397                                         continue;
398                                 } 
399                                 
400                                 if (name == "remove") {
401                                         ReadRemoveSection (reader, configSection);
402                                         continue;
403                                 }
404
405                                 if (name == "clear") {
406                                         if (reader.HasAttributes)
407                                                 ThrowException ("Unrecognized attribute.", reader);
408
409                                         factories.Clear ();
410                                         continue;
411                                 }
412
413                                 if (name == "sectionGroup") {
414                                         ReadSectionGroup (reader, configSection);
415                                         continue;
416                                 }
417
418                                 ThrowException ("Unrecognized element", reader);
419                         }
420                 }
421
422                 private void ThrowException (string text, XmlTextReader reader)
423                 {
424                         throw new ConfigurationException (text, fileName, reader.LineNumber);
425                 }
426         }
427 }
428
429