Extension to commit 17bd0c42c5646b4ab08342fbf8137e7c5bfc0738. This ensures that inher...
[mono.git] / mcs / class / System.Configuration / System.Configuration / ConfigurationElement.cs
1 //
2 // System.Configuration.ConfigurationElement.cs
3 //
4 // Authors:
5 //      Duncan Mak (duncan@ximian.com)
6 //      Lluis Sanchez Gual (lluis@novell.com)
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining
9 // a copy of this software and associated documentation files (the
10 // "Software"), to deal in the Software without restriction, including
11 // without limitation the rights to use, copy, modify, merge, publish,
12 // distribute, sublicense, and/or sell copies of the Software, and to
13 // permit persons to whom the Software is furnished to do so, subject to
14 // the following conditions:
15 // 
16 // The above copyright notice and this permission notice shall be
17 // included in all copies or substantial portions of the Software.
18 // 
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 //
27 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
28 //
29
30 #if NET_2_0
31 using System.Collections;
32 using System.Xml;
33 using System.Reflection;
34 using System.IO;
35 using System.ComponentModel;
36
37 namespace System.Configuration
38 {
39         public abstract class ConfigurationElement
40         {
41                 string rawXml;
42                 bool modified;
43                 ElementMap map;
44                 ConfigurationPropertyCollection keyProps;
45                 ConfigurationElementCollection defaultCollection;
46                 bool readOnly;
47                 ElementInformation elementInfo;
48                 ConfigurationElementProperty elementProperty;
49                 Configuration _configuration;
50
51                 internal Configuration Configuration {
52                         get { return _configuration; }
53                         set { _configuration = value; }
54                 }
55
56                 protected ConfigurationElement ()
57                 {
58                 }
59                 
60                 internal virtual void InitFromProperty (PropertyInformation propertyInfo)
61                 {
62                         elementInfo = new ElementInformation (this, propertyInfo);
63                         Init ();
64                 }
65                 
66                 public ElementInformation ElementInformation {
67                         get {
68                                 if (elementInfo == null)
69                                         elementInfo = new ElementInformation (this, null);
70                                 return elementInfo;
71                         }
72                 }
73
74                 internal string RawXml {
75                         get { return rawXml; }
76                         set {
77                                 // FIXME: this hack is nasty. We should make
78                                 // some refactory on the entire assembly.
79                                 if (rawXml == null || value != null)
80                                         rawXml = value;
81                         }
82                 }
83
84                 protected internal virtual void Init ()
85                 {
86                 }
87
88                 protected internal virtual ConfigurationElementProperty ElementProperty {
89                         get {
90                                 if (elementProperty == null)
91                                         elementProperty = new ConfigurationElementProperty (ElementInformation.Validator);
92                                 return elementProperty;
93                         }
94                 }
95
96                 [MonoTODO]
97                 protected ContextInformation EvaluationContext {
98                         get {
99                                 if (Configuration != null)
100                                         return Configuration.EvaluationContext;
101                                 throw new NotImplementedException ();
102                         }
103                 }
104
105                 ConfigurationLockCollection lockAllAttributesExcept;
106                 public ConfigurationLockCollection LockAllAttributesExcept {
107                         get {
108                                 if (lockAllAttributesExcept == null) {
109                                         lockAllAttributesExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute | ConfigurationLockType.Exclude);
110                                 }
111
112                                 return lockAllAttributesExcept;
113                         }
114                 }
115
116                 ConfigurationLockCollection lockAllElementsExcept;
117                 public ConfigurationLockCollection LockAllElementsExcept {
118                         get {
119                                 if (lockAllElementsExcept == null) {
120                                         lockAllElementsExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Element | ConfigurationLockType.Exclude);
121                                 }
122
123                                 return lockAllElementsExcept;
124                         }
125                 }
126
127                 ConfigurationLockCollection lockAttributes;
128                 public ConfigurationLockCollection LockAttributes {
129                         get {
130                                 if (lockAttributes == null) {
131                                         lockAttributes = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute);
132                                 }
133
134                                 return lockAttributes;
135                         }
136                 }
137
138                 ConfigurationLockCollection lockElements;
139                 public ConfigurationLockCollection LockElements {
140                         get {
141                                 if (lockElements == null) {
142                                         lockElements = new ConfigurationLockCollection (this, ConfigurationLockType.Element);
143                                 }
144
145                                 return lockElements;
146                         }
147                 }
148
149                 bool lockItem;
150                 public bool LockItem {
151                         get { return lockItem; }
152                         set { lockItem = value; }
153                 }
154
155                 [MonoTODO]
156                 protected virtual void ListErrors (IList list)
157                 {
158                         throw new NotImplementedException ();
159                 }
160
161                 [MonoTODO]
162                 protected void SetPropertyValue (ConfigurationProperty prop, object value, bool ignoreLocks)
163                 {
164                         try {
165                                 /* XXX all i know for certain is that Validation happens here */
166                                 prop.Validate (value);
167
168                                 /* XXX presumably the actual setting of the
169                                  * property happens here instead of in the
170                                  * set_Item code below, but that would mean
171                                  * the Value needs to be stuffed in the
172                                  * property, not the propertyinfo (or else the
173                                  * property needs a ref to the property info
174                                  * to correctly set the value). */
175                         }
176                         catch (Exception e) {
177                                 throw new ConfigurationErrorsException (String.Format ("The value for the property '{0}' is not valid. The error is: {1}", prop.Name, e.Message), e);
178                         }
179                 }
180
181                 internal ConfigurationPropertyCollection GetKeyProperties ()
182                 {
183                         if (keyProps != null) return keyProps;
184                         
185                         ConfigurationPropertyCollection tmpkeyProps = new ConfigurationPropertyCollection ();
186                                 foreach (ConfigurationProperty prop in Properties) {
187                                         if (prop.IsKey)
188                                         tmpkeyProps.Add (prop);
189                                 }
190
191                         return keyProps = tmpkeyProps;
192                 }
193
194                 internal ConfigurationElementCollection GetDefaultCollection ()
195                 {
196                         if (defaultCollection != null) return defaultCollection;
197
198                         ConfigurationProperty defaultCollectionProp = null;
199
200                         foreach (ConfigurationProperty prop in Properties) {
201                                 if (prop.IsDefaultCollection) {
202                                         defaultCollectionProp = prop;
203                                         break;
204                                 }
205                         }
206
207                         if (defaultCollectionProp != null) {
208                                 defaultCollection = this [defaultCollectionProp] as ConfigurationElementCollection;
209                         }
210
211                         return defaultCollection;
212                 }
213
214                 protected internal object this [ConfigurationProperty property] {
215                         get { return this [property.Name]; }
216                         set { this [property.Name] = value; }
217                 }
218
219                 protected internal object this [string property_name] {
220                         get {
221                                 PropertyInformation pi = ElementInformation.Properties [property_name];
222                                 if (pi == null)
223                                         throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
224
225                                 return pi.Value;
226                         }
227
228                         set {
229                                 PropertyInformation pi = ElementInformation.Properties [property_name];
230                                 if (pi == null)
231                                         throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
232
233                                 SetPropertyValue (pi.Property, value, false);
234
235                                 pi.Value = value;
236                                 modified = true;
237                         }
238                 }
239
240                 protected internal virtual ConfigurationPropertyCollection Properties {
241                         get {
242                                 if (map == null)
243                                         map = ElementMap.GetMap (GetType());
244                                 return map.Properties;
245                         }
246                 }
247
248                 public override bool Equals (object compareTo)
249                 {
250                         ConfigurationElement other = compareTo as ConfigurationElement;
251                         if (other == null) return false;
252                         if (GetType() != other.GetType()) return false;
253                         
254                         foreach (ConfigurationProperty prop in Properties) {
255                                 if (!object.Equals (this [prop], other [prop]))
256                                         return false;
257                         }
258                         return true;
259                 }
260
261                 public override int GetHashCode ()
262                 {
263                         int code = 0;
264                         object o;
265                         
266                         foreach (ConfigurationProperty prop in Properties) {
267                                 o = this [prop];
268                                 if (o == null)
269                                         continue;
270                                 
271                                 code += o.GetHashCode ();
272                         }
273                         
274                         return code;
275                 }
276
277                 internal virtual bool HasValues ()
278                 {
279                         foreach (PropertyInformation pi in ElementInformation.Properties)
280                                 if (pi.ValueOrigin != PropertyValueOrigin.Default)
281                                         return true;
282                         
283                         return false;
284                 }
285
286                 internal virtual bool HasLocalModifications ()
287                 {
288                         foreach (PropertyInformation pi in ElementInformation.Properties)
289                                 if (pi.ValueOrigin == PropertyValueOrigin.SetHere && pi.IsModified)
290                                         return true;
291                         
292                         return false;
293                 }
294                 
295                 protected internal virtual void DeserializeElement (XmlReader reader, bool serializeCollectionKey)
296                 {
297                         Hashtable readProps = new Hashtable ();
298                         
299                         reader.MoveToContent ();
300                         while (reader.MoveToNextAttribute ())
301                         {
302                                 PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
303                                 if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
304                                         /* handle the built in ConfigurationElement attributes here */
305                                         if (reader.LocalName == "lockAllAttributesExcept") {
306                                                 LockAllAttributesExcept.SetFromList (reader.Value);
307                                         }
308                                         else if (reader.LocalName == "lockAllElementsExcept") {
309                                                 LockAllElementsExcept.SetFromList (reader.Value);
310                                         }
311                                         else if (reader.LocalName == "lockAttributes") {
312                                                 LockAttributes.SetFromList (reader.Value);
313                                         }
314                                         else if (reader.LocalName == "lockElements") {
315                                                 LockElements.SetFromList (reader.Value);
316                                         }
317                                         else if (reader.LocalName == "lockItem") {
318                                                 LockItem = (reader.Value.ToLowerInvariant () == "true");
319                                         }
320                                         else if (reader.LocalName == "xmlns") {
321                                                 /* ignore */
322                                         } else if (this is ConfigurationSection && reader.LocalName == "configSource") {
323                                                 /* ignore */
324                                         } else if (!OnDeserializeUnrecognizedAttribute (reader.LocalName, reader.Value))
325                                                 throw new ConfigurationErrorsException ("Unrecognized attribute '" + reader.LocalName + "'.", reader);
326
327                                         continue;
328                                 }
329                                 
330                                 if (readProps.ContainsKey (prop))
331                                         throw new ConfigurationErrorsException ("The attribute '" + prop.Name + "' may only appear once in this element.", reader);
332
333                                 string value = null;
334                                 try {
335                                         value = reader.Value;
336                                         ValidateValue (prop.Property, value);
337                                         prop.SetStringValue (value);
338                                 } catch (ConfigurationErrorsException) {
339                                         throw;
340                                 } catch (ConfigurationException) {
341                                         throw;
342                                 } catch (Exception ex) {
343                                         string msg = String.Format ("The value for the property '{0}' is not valid. The error is: {1}", prop.Name, ex.Message);
344                                         throw new ConfigurationErrorsException (msg, reader);
345                                 }
346                                 readProps [prop] = prop.Name;
347                                 
348                                 ConfigXmlTextReader _reader = reader as ConfigXmlTextReader;
349                                 
350                                 prop.Source = _reader.Filename;
351                                 prop.LineNumber = _reader.LineNumber;
352                         }
353                         
354                         reader.MoveToElement ();
355                         if (reader.IsEmptyElement) {
356                                 reader.Skip ();
357                         } else {
358                                 int depth = reader.Depth;
359
360                                 reader.ReadStartElement ();
361                                 reader.MoveToContent ();
362
363                                 do {
364                                         if (reader.NodeType != XmlNodeType.Element) {
365                                                 reader.Skip ();
366                                                 continue;
367                                         }
368                                         
369                                         PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
370                                         if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
371                                                 if (!OnDeserializeUnrecognizedElement (reader.LocalName, reader)) {
372                                                         if (prop == null) {
373                                                                 ConfigurationElementCollection c = GetDefaultCollection ();
374                                                                 if (c != null && c.OnDeserializeUnrecognizedElement (reader.LocalName, reader))
375                                                                         continue;
376                                                         }
377                                                         throw new ConfigurationErrorsException ("Unrecognized element '" + reader.LocalName + "'.", reader);
378                                                 }
379                                                 continue;
380                                         }
381                                         
382                                         if (!prop.IsElement)
383                                                 throw new ConfigurationException ("Property '" + prop.Name + "' is not a ConfigurationElement.");
384                                         
385                                         if (readProps.Contains (prop))
386                                                 throw new ConfigurationErrorsException ("The element <" + prop.Name + "> may only appear once in this section.", reader);
387                                         
388                                         ConfigurationElement val = (ConfigurationElement) prop.Value;
389                                         val.DeserializeElement (reader, serializeCollectionKey);
390                                         readProps [prop] = prop.Name;
391
392                                         if(depth == reader.Depth)
393                                                 reader.Read();
394
395                                 } while (depth < reader.Depth);                         
396                         }
397                         
398                         modified = false;
399                                 
400                         foreach (PropertyInformation prop in ElementInformation.Properties)
401                                 if (!String.IsNullOrEmpty(prop.Name) && prop.IsRequired && !readProps.ContainsKey (prop)) {
402                                         PropertyInformation p = ElementInformation.Properties [prop.Name];
403                                         if (p == null) {
404                                                 object val = OnRequiredPropertyNotFound (prop.Name);
405                                                 if (!object.Equals (val, prop.DefaultValue)) {
406                                                         prop.Value = val;
407                                                         prop.IsModified = false;
408                                                 }
409                                         }
410                                 }
411
412                         PostDeserialize ();
413                 }
414
415                 protected virtual bool OnDeserializeUnrecognizedAttribute (string name, string value)
416                 {
417                         return false;
418                 }
419
420                 protected virtual bool OnDeserializeUnrecognizedElement (string element, XmlReader reader)
421                 {
422                         return false;
423                 }
424                 
425                 protected virtual object OnRequiredPropertyNotFound (string name)
426                 {
427                         throw new ConfigurationErrorsException ("Required attribute '" + name + "' not found.");
428                 }
429                 
430                 protected virtual void PreSerialize (XmlWriter writer)
431                 {
432                 }
433
434                 protected virtual void PostDeserialize ()
435                 {
436                 }
437
438                 protected internal virtual void InitializeDefault ()
439                 {
440                 }
441
442                 protected internal virtual bool IsModified ()
443                 {
444                         return modified;
445                 }
446                 
447                 protected internal virtual void SetReadOnly ()
448                 {
449                         readOnly = true;
450                 }
451                 
452                 public virtual bool IsReadOnly ()
453                 {
454                         return readOnly;
455                 }
456
457                 protected internal virtual void Reset (ConfigurationElement parentElement)
458                 {
459                         if (parentElement != null)
460                                 ElementInformation.Reset (parentElement.ElementInformation);
461                         else
462                                 InitializeDefault ();
463                 }
464
465                 protected internal virtual void ResetModified ()
466                 {
467                         modified = false;
468                         foreach (PropertyInformation p in ElementInformation.Properties)
469                                 p.IsModified = false;
470                 }
471
472                 protected internal virtual bool SerializeElement (XmlWriter writer, bool serializeCollectionKey)
473                 {
474                         PreSerialize (writer);
475                         
476                         if (serializeCollectionKey) {
477                                 ConfigurationPropertyCollection props = GetKeyProperties ();
478                                 foreach (ConfigurationProperty prop in props)
479                                         writer.WriteAttributeString (prop.Name, prop.ConvertToString (this[prop.Name]));
480                                 return props.Count > 0;
481                         }
482                         
483                         bool wroteData = false;
484                         
485                         foreach (PropertyInformation prop in ElementInformation.Properties)
486                         {
487                                 if (prop.IsElement || prop.ValueOrigin == PropertyValueOrigin.Default)
488                                         continue;
489                                 
490                                 if (!object.Equals (prop.Value, prop.DefaultValue)) {
491                                         writer.WriteAttributeString (prop.Name, prop.GetStringValue ());
492                                         wroteData = true;
493                                 }
494                         }
495                         
496                         foreach (PropertyInformation prop in ElementInformation.Properties)
497                         {
498                                 if (!prop.IsElement)
499                                         continue;
500                                 
501                                 ConfigurationElement val = (ConfigurationElement) prop.Value;
502                                 if (val != null)
503                                         wroteData = val.SerializeToXmlElement (writer, prop.Name) || wroteData;
504                         }
505                         return wroteData;
506                 }
507                                 
508                 protected internal virtual bool SerializeToXmlElement (
509                                 XmlWriter writer, string elementName)
510                 {
511                         if (!HasValues ())
512                                 return false;
513
514                         if (elementName != null && elementName != "")
515                                 writer.WriteStartElement (elementName);
516                         bool res = SerializeElement (writer, false);
517                         if (elementName != null && elementName != "")
518                                 writer.WriteEndElement ();
519                         return res;
520                 }
521
522                 protected internal virtual void Unmerge (
523                                 ConfigurationElement source, ConfigurationElement parent,
524                                 ConfigurationSaveMode updateMode)
525                 {
526                         if (parent != null && source.GetType() != parent.GetType())
527                                 throw new ConfigurationException ("Can't unmerge two elements of different type");
528                         
529                         foreach (PropertyInformation prop in source.ElementInformation.Properties)
530                         {
531                                 if (prop.ValueOrigin == PropertyValueOrigin.Default)
532                                         continue;
533                                 
534                                 PropertyInformation unmergedProp = ElementInformation.Properties [prop.Name];
535                                 
536                                 object sourceValue = prop.Value;
537                                 if      (parent == null || !parent.HasValue (prop.Name)) {
538                                         unmergedProp.Value = sourceValue;
539                                         continue;
540                                 }
541                                 else if (sourceValue != null) {
542                                         object parentValue = parent [prop.Name];
543                                         if (prop.IsElement) {
544                                                 if (parentValue != null) {
545                                                         ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
546                                                         copy.Unmerge ((ConfigurationElement) sourceValue, (ConfigurationElement) parentValue, updateMode);
547                                                 }
548                                                 else
549                                                         unmergedProp.Value = sourceValue;
550                                         }
551                                         else {
552                                                 if (!object.Equals (sourceValue, parentValue) || 
553                                                         (updateMode == ConfigurationSaveMode.Full) ||
554                                                         (updateMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
555                                                         unmergedProp.Value = sourceValue;
556                                         }
557                                 }
558                         }
559                 }
560                 
561                 internal bool HasValue (string propName)
562                 {
563                         PropertyInformation info = ElementInformation.Properties [propName];
564                         return info != null && info.ValueOrigin != PropertyValueOrigin.Default;
565                 }
566                 
567                 internal bool IsReadFromConfig (string propName)
568                 {
569                         PropertyInformation info = ElementInformation.Properties [propName];
570                         return info != null && info.ValueOrigin == PropertyValueOrigin.SetHere;
571                 }
572
573                 void ValidateValue (ConfigurationProperty p, string value)
574                 {
575                         ConfigurationValidatorBase validator;
576                         if (p == null || (validator = p.Validator) == null)
577                                 return;
578                         
579                         if (!validator.CanValidate (p.Type))
580                                 throw new ConfigurationException (
581                                         String.Format ("Validator does not support type {0}", p.Type));
582                         validator.Validate (p.ConvertFromString (value));
583                 }
584         }
585         
586         internal class ElementMap
587         {
588 #if TARGET_JVM
589                 const string elementMapsKey = "ElementMap_elementMaps";
590                 static Hashtable elementMaps
591                 {
592                         get
593                         {
594                                 Hashtable tbl = (Hashtable) AppDomain.CurrentDomain.GetData (elementMapsKey);
595                                 if (tbl == null) {
596                                         lock (typeof (ElementMap)) {
597                                                 tbl = (Hashtable) AppDomain.CurrentDomain.GetData (elementMapsKey);
598                                                 if (tbl == null) {
599                                                         tbl = Hashtable.Synchronized (new Hashtable ());
600                                                         AppDomain.CurrentDomain.SetData (elementMapsKey, tbl);
601                                                 }
602                                         }
603                                 }
604                                 return tbl;
605                         }
606                 }
607 #else
608                 static readonly Hashtable elementMaps = Hashtable.Synchronized (new Hashtable ());
609 #endif
610
611                 readonly ConfigurationPropertyCollection properties;
612                 readonly ConfigurationCollectionAttribute collectionAttribute;
613
614                 public static ElementMap GetMap (Type t)
615                 {
616                         ElementMap map = elementMaps [t] as ElementMap;
617                         if (map != null) return map;
618                         map = new ElementMap (t);
619                         elementMaps [t] = map;
620                         return map;
621                 }
622                 
623                 public ElementMap (Type t)
624                 {
625                         properties = new ConfigurationPropertyCollection ();
626                 
627                         collectionAttribute = Attribute.GetCustomAttribute (t, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
628                         
629                         PropertyInfo[] props = t.GetProperties (BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance);
630                         foreach (PropertyInfo prop in props)
631                         {
632                                 ConfigurationPropertyAttribute at = Attribute.GetCustomAttribute (prop, typeof(ConfigurationPropertyAttribute)) as ConfigurationPropertyAttribute;
633                                 if (at == null) continue;
634                                 string name = at.Name != null ? at.Name : prop.Name;
635
636                                 ConfigurationValidatorAttribute validatorAttr = Attribute.GetCustomAttribute (prop, typeof (ConfigurationValidatorAttribute)) as ConfigurationValidatorAttribute;
637                                 ConfigurationValidatorBase validator = validatorAttr != null ? validatorAttr.ValidatorInstance : null;
638
639                                 TypeConverterAttribute convertAttr = (TypeConverterAttribute) Attribute.GetCustomAttribute (prop, typeof (TypeConverterAttribute));
640                                 TypeConverter converter = convertAttr != null ? (TypeConverter) Activator.CreateInstance (Type.GetType (convertAttr.ConverterTypeName), true) : null;
641                                 ConfigurationProperty cp = new ConfigurationProperty (name, prop.PropertyType, at.DefaultValue, converter, validator, at.Options);
642
643                                 cp.CollectionAttribute = Attribute.GetCustomAttribute (prop, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;                             
644                                 properties.Add (cp);
645                         }
646                 }
647
648                 public ConfigurationCollectionAttribute CollectionAttribute
649                 {
650                         get { return collectionAttribute; }
651                 }
652                 
653                 public bool HasProperties
654                 {
655                         get { return properties.Count > 0; }
656                 }
657                 
658                 public ConfigurationPropertyCollection Properties
659                 {
660                         get {
661                                 return properties;
662                         }
663                 }
664         }
665 }
666
667 #endif