2002-10-06 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System / System.Configuration / ConfigurationSettings.cs
index 909dcdf6992fa91488c04f31bd102ec95fa34356..ba1420340fc38366cf9e7aced0b1ec785c548685 100644 (file)
 //
 // Author:
 //   Christopher Podurgiel (cpodurgiel@msn.com)
+//   Gonzalo Paniagua Javier (gonzalo@ximian.com)
 //
 // C) Christopher Podurgiel
+// (c) 2002 Ximian, Inc. (http://www.ximian.com)
 //
 
 using System;
+using System.Collections;
 using System.Collections.Specialized;
+using System.IO;
 using System.Reflection;
 using System.Xml;
 using System.Xml.XPath;
 
 namespace System.Configuration
 {
-       /// <summary>
-       ///             Component class.
-       /// </summary>
-       /// <remarks>
-       ///             Longer description
-       /// </remarks>
-
        public sealed class ConfigurationSettings
        {
+               static IConfigurationSystem config;
+                       
+               private ConfigurationSettings ()
+               {
+               }
 
-               private static string applicationConfigFileName;
-               
-               /// <summary>
-               ///             ConfigurationSettings Constructor.
-               /// </summary>
-               public ConfigurationSettings ()
+               public static object GetConfig (string sectionName)
                {
-                       
+                       if (config == null)
+                               config = DefaultConfig.GetInstance ();
+
+                       return config.GetConfig (sectionName);
                }
 
-               /// <summary>
-               ///             Returns configuration settings for a user-defined configuration section.
-               /// </summary>
-               /// <param name="sectionName">The name of the configuration section that configuration settings are read from.</param>
-               /// <returns></returns>
-               public static object GetConfig(string sectionName)
-               {
-                       //Create an instance of an XML Document.
-                       XmlDocument ConfigurationDocument = new XmlDocument();
-
-                       /*
-                        * LAMESPEC: The .config file that needs to be parsed is the name of the application, plus ".config"
-                        * ie. "Myapplication.exe.config"
-                        * The only way I could find to get the name of the application is through System.Forms.Application.ExecutablePath, this
-                        * may be an incorrect way to get this information.  It works properly on a windows machine when building an executable,
-                        * however, I'm not sure how this would work under other platforms.
-                       */
-                       //Get the full path to the Applicaton Configuration File.
-                       applicationConfigFileName =  "FIXME:ConfigurationSettings" + ".config";
-
-                       //Try to load the XML Document.
-                       try
-                       { 
-                               ConfigurationDocument.Load(applicationConfigFileName);
+               public static NameValueCollection AppSettings
+               {
+                       get {
+                               object appSettings = GetConfig ("appSettings");
+                               if (appSettings == null)
+                                       appSettings = new NameValueCollection ();
+
+                               return (NameValueCollection) appSettings;
                        }
-                       catch(XmlException e)
-                       {
-                               //Error loading the XML Document.  Throw a ConfigurationException.
-                               throw(new ConfigurationException(e.Message, applicationConfigFileName, e.LineNumber));
+               }
+
+       }
+
+       //
+       // class DefaultConfig: read configuration from machine.config file and application
+       // config file if available.
+       //
+       class DefaultConfig : IConfigurationSystem
+       {
+               static string creatingInstance = "137213797382-asad";
+               static string buildingData = "1797382-ladgasjkdg";
+               static DefaultConfig instance;
+               ConfigurationData config;
+
+               private DefaultConfig ()
+               {
+               }
+
+               public static DefaultConfig GetInstance ()
+               {
+                       if (instance == null) {
+                               lock (creatingInstance) {
+                                       if (instance == null) {
+                                               instance = new DefaultConfig ();
+                                               instance.Init ();
+                                       }
+                                       
+                               }
                        }
 
-                               string sectionHandlerName = GetSectionHanderType(ConfigurationDocument, sectionName);
-                            
-                               XmlNode sectionNode = ConfigurationDocument.SelectSingleNode("/configuration/" + sectionName);
-                       
-                       
-                       
-                       //If the specified sectionName is not found, then sectionNode will be null.  When calling objNVSHandler.Create(),
-                       //sectionNode cannot be null.
-                        if(sectionNode == null)
-                       {
+                       return instance;
+               }
+
+               public object GetConfig (string sectionName)
+               {
+                       return config.GetConfig (sectionName);
+               }
+
+               public void Init ()
+               {
+                       if (config == null)
+                               lock (buildingData) {
+                                       if (config != null)
+                                               return;
+
+                                       ConfigurationData data = new ConfigurationData ();
+                                       if (data.Load (GetMachineConfigPath ())) {
+                                               ConfigurationData appData = new ConfigurationData (data);
+                                               appData.Load (GetAppConfigPath ());
+                                               config = appData;
+                                       }
+                               }
+               }
+
+               private static string GetMachineConfigPath ()
+               {
+                       string location = typeof (string).Assembly.Location;
+                       // Workaround for bug #31730
+                       int index = location.IndexOf ("install");
+                       location = Path.Combine (location.Substring (0, index + 7), "lib");
+                       // 
+                       return Path.Combine (location, "machine.config");
+               }
+
+               private static string GetAppConfigPath ()
+               {
+                       AppDomainSetup currentInfo = AppDomain.CurrentDomain.SetupInformation;
+
+                       string appBase = currentInfo.ApplicationBase;
+                       string configFile = currentInfo.ConfigurationFile;
+                       // FIXME: need to check out default domain configuration file name
+                       if (configFile == null || configFile.Length == 0)
                                return null;
+
+                       return Path.Combine (appBase, configFile);
+               }
+       }
+
+       class ConfigurationData
+       {
+               ConfigurationData parent;
+               Hashtable factories;
+               string fileName;
+               object removedMark = new object ();
+               object groupMark = new object ();
+
+               public ConfigurationData () : this (null)
+               {
+               }
+
+               public ConfigurationData (ConfigurationData parent)
+               {
+                       this.parent = parent;
+                       factories = new Hashtable ();
+               }
+
+               public bool Load (string fileName)
+               {
+                       if (fileName == null)
+                               return false;
+
+                       this.fileName = fileName;
+                       XmlTextReader reader = null;
+
+                       try {
+                               reader = new XmlTextReader (fileName);
+                               InitRead (reader);
+                               MoveToNextElement (reader);
+                               ReadSections (reader, null);
+                       } finally {
+                               if (reader == null)
+                                       reader.Close();
                        }
-                       
 
-                       //Create a new SectionHandler\r
-\r
-                       //According to the Docs provided by Microsoft, the user can create their own configuration sections, and create a custom\r
-                       //handler class for it. The user would specify the class and its assebly in the <configSections> section.  These would be\r
-                       //seperated by a comma.\r
-\r
-                       string sectionHandlerClassName = sectionHandlerName;
-                       string sectionHandlerAssemblyName = "System";\r
-\r
-                       //Split the SectionHandler Class Name from the Assembly Name (if provided).\r
-                       string[] sectionHandlerArray = sectionHandlerName.Split(new char[]{','}, 2);
-                       if(sectionHandlerArray.Length == 2)
-                       {
-                               sectionHandlerClassName = sectionHandlerArray[0];
-                               sectionHandlerAssemblyName = sectionHandlerArray[1];
+                       return true;
+               }
+
+               IConfigurationSectionHandler GetSectionHandler (string sectionName)
+               {
+                       if (!factories.Contains (sectionName)) {
+                               if (parent == null)
+                                       return null;
+
+                               return parent.GetConfig (sectionName) as IConfigurationSectionHandler;
                        }
                        
-                       // Load the assembly to use.
-                       Assembly assem = Assembly.Load(sectionHandlerAssemblyName);
-                       //Get the class type.
-                       Type handlerObjectType = assem.GetType(sectionHandlerClassName);
-                       //Get a reference to the method "Create"
-                       MethodInfo createMethod = handlerObjectType.GetMethod("Create");
-                       //Create an Instance of this SectionHandler.
-                       Object objSectionHandler = Activator.CreateInstance(handlerObjectType);
-
-                       //define the arguments to be passed to the "Create" Method.
-                       Object[] args = new Object[3];
-                       args[0] = null;
-                       args[1] = null;
-                       args[2] = sectionNode;
-
-                       object sectionHandlerCollection = createMethod.Invoke(objSectionHandler, args);
-
-                       //Return the collection
-                       return sectionHandlerCollection;
-
-               }
-
-
-               /// <summary>\r
-               ///             Gets the name of the SectionHander Class that will handle this section.\r
-               /// </summary>\r
-               /// <param name="xmlDoc">An xml Configuration Document.</param>\r
-               /// <param name="sectionName">The name of the configuration section that configuration settings are read from.</param>\r
-               /// <returns>The name of the Handler Object for this configuration section, including the name if its Assembly.</returns>
-               [MonoTODO]
-               private static string GetSectionHanderType(XmlDocument xmlDoc, string sectionName)
-               {
-                       //TODO: This method does not account for sectionGroups yet.
-                       string handlerName = null;
-
-                       //<appSettings> is a predefined configuration section. It does not have a definition
-                       // in the <configSections> section, and will always be handled by the NameValueSectionHandler.
-                       if(sectionName == "appSettings")
-                       {
-                               handlerName = "System.Configuration.NameValueSectionHandler";
+                       object o = factories [sectionName];
+                       if (o == removedMark)
+                               return null;
+
+                       if (o is IConfigurationSectionHandler)
+                               return (IConfigurationSectionHandler) o;
+
+                       Type iconfig = typeof (IConfigurationSectionHandler);
+                       string [] typeInfo = ((string) o).Split (',');
+                       Type t;
+                       
+                       // Hack. Type.GetType should be enough
+                       if (typeInfo.Length > 1) {
+                               Assembly ass = Assembly.Load (typeInfo [1].Trim ());
+                               t = ass.GetType (typeInfo [0].Trim ());
+                       } else {
+                               t = Type.GetType (typeInfo [0]);
                        }
-                       else
-                       {
-                               
-                               string[] sectionPathArray = sectionName.Split(new char[]{'/'});
-
-                               //Build an XPath statement.
-                               string xpathStatement = "/configuration/configSections";
-                               for (int i=0; i < sectionPathArray.Length; i++)
-                               {
-                                       if(i < sectionPathArray.Length - 1)
-                                       {
-                                               xpathStatement = xpathStatement + "/sectionGroup[@name='" + sectionPathArray[i] + "']";
-                                       }
-                                       else
-                                       {
-                                               xpathStatement = xpathStatement + "/section[@name='" + sectionPathArray[i] + "']";
+                       
+                       if (t == null)
+                               throw new ConfigurationException ("Cannot get Type for " + o);
+
+                       if (!iconfig.IsAssignableFrom (t))
+                               throw new ConfigurationException (sectionName + " does not implement " + iconfig);
+                       
+                       o = Activator.CreateInstance (t, true);
+                       if (o == null)
+                               throw new ConfigurationException ("Cannot get instance for " + t);
+                       
+                       return (IConfigurationSectionHandler) o;
+               }
+
+               //TODO: Should use XPath when it works properly for this.
+               XmlDocument GetDocumentForSection (string sectionName)
+               {
+                       ConfigXmlDocument doc = new ConfigXmlDocument ();
+                       XmlTextReader reader = new XmlTextReader (fileName);
+                       InitRead (reader);
+                       string [] sectionPath = sectionName.Split ('/');
+                       int i = 0;
+                       if (!reader.EOF) {
+                               reader.Skip ();
+                               while (!reader.EOF) {
+                                       if (reader.NodeType == XmlNodeType.Element &&
+                                           reader.Name == sectionPath [i]) {
+                                               if (++i == sectionPath.Length) {
+                                                       doc.LoadSingleElement (fileName, reader);
+                                                       break;
+                                               }
+                                               MoveToNextElement (reader);
+                                               continue;
                                        }
+                                       reader.Skip ();
+                                       if (reader.NodeType != XmlNodeType.Element)
+                                               MoveToNextElement (reader);
                                }
-                               
-                               //Get all of the <section> node using the xpath statement.
-                               XmlNode sectionNode = xmlDoc.SelectSingleNode(xpathStatement);
+                       }
 
-                               // if this section isn't found, then there was something wrong with the config document,
-                               // or the sectionName didn't have a proper definition.
-                               if(sectionNode == null)
-                               {
-                                       
-                                       throw (new ConfigurationException("Unrecognized element."));
+                       reader.Close ();
+                       return doc;
+               }
+               
+               public object GetConfig (string sectionName)
+               {
+                       IConfigurationSectionHandler handler = GetSectionHandler (sectionName);
+                       if (handler == null)
+                               return null;
+
+                       factories [sectionName] = handler;
+
+                       XmlDocument doc = GetDocumentForSection (sectionName);
+                       if (doc == null)
+                               throw new ConfigurationException ("Section not found: " + sectionName);
+
+                       return handler.Create (null, null, doc);
+               }
+
+               private object LookForFactory (string key)
+               {
+                       object o = factories [key];
+                       if (o != null)
+                               return o;
+
+                       if (parent != null)
+                               return parent.LookForFactory (key);
+
+                       return null;
+               }
+               
+               private void InitRead (XmlTextReader reader)
+               {
+                       reader.MoveToContent ();
+                       if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
+                               ThrowException ("Configuration file does not have a valid root element", reader);
+
+                       if (reader.HasAttributes)
+                               ThrowException ("Unrecognized attribute in root element", reader);
+
+                       MoveToNextElement (reader);
+                       if (reader.Depth == 1 && reader.Name == "configSections") {
+                               if (reader.HasAttributes)
+                                       ThrowException ("Unrecognized attribute in configSections element.",
+                                                       reader);
+                       }
+               }
+
+               private void MoveToNextElement (XmlTextReader reader)
+               {
+                       while (reader.Read ()) {
+                               XmlNodeType ntype = reader.NodeType;
+                               if (ntype == XmlNodeType.Element)
+                                       return;
+
+                               if (ntype != XmlNodeType.Whitespace &&
+                                   ntype != XmlNodeType.Comment &&
+                                   ntype != XmlNodeType.SignificantWhitespace &&
+                                   ntype != XmlNodeType.EndElement)
+                                       ThrowException ("Unrecognized element", reader);
+                       }
+               }
+
+               private void ReadSection (XmlTextReader reader, string sectionName)
+               {
+                       string attName;
+                       string nameValue = null;
+                       string typeValue = null;
+
+                       while (reader.MoveToNextAttribute ()) {
+                               attName = reader.Name;
+                               if (attName == null)
+                                       continue;
+
+                               if (attName == "allowLocation" || attName == "allowDefinition")
+                                       continue;
+
+                               if (attName == "type")  {
+                                       if (typeValue != null)
+                                               ThrowException ("Duplicated type attribute.", reader);
+                                       typeValue = reader.Value;
+                                       continue;
                                }
+                               
+                               if (attName == "name")  {
+                                       if (nameValue != null)
+                                               ThrowException ("Duplicated name attribute.", reader);
+                                       nameValue = reader.Value;
+                                       continue;
+                               }
+
+                               ThrowException ("Unrecognized attribute.", reader);
+                       }
+
+                       if (nameValue == null || typeValue == null)
+                               ThrowException ("Required attribute missing", reader);
 
-                               handlerName =  sectionNode.Attributes["type"].Value;
+                       if (sectionName != null)
+                               nameValue = sectionName + '/' + nameValue;
 
-                       }\r
+                       reader.MoveToElement();
+                       object o = LookForFactory (nameValue);
+                       if (o != null && o != removedMark)
+                               ThrowException ("Already have a factory for " + nameValue, reader);
 
-                       //Return the name of the handler.
-                       return handlerName;
+                       factories [nameValue] = typeValue;
+                       MoveToNextElement (reader);
                }
 
+               private void ReadRemoveSection (XmlTextReader reader, string sectionName)
+               {
+                       if (!reader.MoveToNextAttribute () || reader.Name != "name")
+                               ThrowException ("Unrecognized attribute.", reader);
 
+                       string removeValue = reader.Value;
+                       if (removeValue == null || removeValue.Length == 0)
+                               ThrowException ("Empty name to remove", reader);
 
-               /// <summary>
-               ///             Get the Application Configuration Settings.
-               /// </summary>
-               public static NameValueCollection AppSettings
+                       reader.MoveToElement ();
+
+                       if (sectionName != null)
+                               removeValue = sectionName + '/' + removeValue;
+
+                       object o = LookForFactory (removeValue);
+                       if (o != null && o != removedMark)
+                               ThrowException ("No factory for " + removeValue, reader);
+
+                       factories [removeValue] = removedMark;
+                       MoveToNextElement (reader);
+               }
+
+               private void ReadSectionGroup (XmlTextReader reader, string configSection)
+               {
+                       if (!reader.MoveToNextAttribute ())
+                               ThrowException ("sectionGroup must have a 'name' attribute.", reader);
+
+                       if (reader.Name != "name")
+                               ThrowException ("Unrecognized attribute.", reader);
+
+                       if (reader.MoveToNextAttribute ())
+                               ThrowException ("Unrecognized attribute.", reader);
+
+                       string value = reader.Value;
+                       if (configSection != null)
+                               value = configSection + '/' + value;
+
+                       object o = LookForFactory (value);
+                       if (o != null && o != removedMark)
+                               ThrowException ("Already have a factory for " + value, reader);
+
+                       factories [value] = groupMark;
+                       MoveToNextElement (reader);
+                       ReadSections (reader, value);
+               }
+
+               private void ReadSections (XmlTextReader reader, string configSection)
                {
-                       get
-                       {       
-                               //Get the Configuration Settings for the "appSettings" section.
-                               NameValueCollection appSettings = (NameValueCollection)GetConfig("appSettings");;
+                       int depth = reader.Depth;
+                       while (reader.Depth == depth) {
+                               string name = reader.Name;
+                               if (reader.Name == null)
+                                       continue;
+                                       
+                               if (name == "section") {
+                                       ReadSection (reader, configSection);
+                                       continue;
+                               } 
+                               
+                               if (name == "remove") {
+                                       ReadRemoveSection (reader, configSection);
+                                       continue;
+                               }
+
+                               if (name == "clear") {
+                                       if (reader.HasAttributes)
+                                               ThrowException ("Unrecognized attribute.", reader);
+
+                                       factories.Clear ();
+                                       continue;
+                               }
 
-                               return appSettings;
+                               if (name == "sectionGroup") {
+                                       ReadSectionGroup (reader, configSection);
+                                       continue;
+                               }
+
+                               ThrowException ("Unrecognized element", reader);
                        }
                }
 
+               private void ThrowException (string text, XmlTextReader reader)
+               {
+                       throw new ConfigurationException (text, fileName, reader.LineNumber);
+               }
        }
 }