[Cleanup] Removed TARGET_JVM
[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 //      Martin Baulig <martin.baulig@xamarin.com>
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
29 // Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
30 //
31
32 using System.Collections;
33 using System.Xml;
34 using System.Reflection;
35 using System.IO;
36 using System.ComponentModel;
37
38 namespace System.Configuration
39 {
40         public abstract class ConfigurationElement
41         {
42                 string rawXml;
43                 bool modified;
44                 ElementMap map;
45                 ConfigurationPropertyCollection keyProps;
46                 ConfigurationElementCollection defaultCollection;
47                 bool readOnly;
48                 ElementInformation elementInfo;
49                 ConfigurationElementProperty elementProperty;
50                 Configuration _configuration;
51                 bool elementPresent;
52
53                 internal Configuration Configuration {
54                         get { return _configuration; }
55                         set { _configuration = value; }
56                 }
57
58                 protected ConfigurationElement ()
59                 {
60                 }
61                 
62                 internal virtual void InitFromProperty (PropertyInformation propertyInfo)
63                 {
64                         elementInfo = new ElementInformation (this, propertyInfo);
65                         Init ();
66                 }
67                 
68                 public ElementInformation ElementInformation {
69                         get {
70                                 if (elementInfo == null)
71                                         elementInfo = new ElementInformation (this, null);
72                                 return elementInfo;
73                         }
74                 }
75
76                 internal string RawXml {
77                         get { return rawXml; }
78                         set {
79                                 // FIXME: this hack is nasty. We should make
80                                 // some refactory on the entire assembly.
81                                 if (rawXml == null || value != null)
82                                         rawXml = value;
83                         }
84                 }
85
86                 protected internal virtual void Init ()
87                 {
88                 }
89
90                 protected internal virtual ConfigurationElementProperty ElementProperty {
91                         get {
92                                 if (elementProperty == null)
93                                         elementProperty = new ConfigurationElementProperty (ElementInformation.Validator);
94                                 return elementProperty;
95                         }
96                 }
97
98                 protected ContextInformation EvaluationContext {
99                         get {
100                                 if (Configuration != null)
101                                         return Configuration.EvaluationContext;
102                                 throw new ConfigurationErrorsException (
103                                         "This element is not currently associated with any context.");
104                         }
105                 }
106
107                 ConfigurationLockCollection lockAllAttributesExcept;
108                 public ConfigurationLockCollection LockAllAttributesExcept {
109                         get {
110                                 if (lockAllAttributesExcept == null) {
111                                         lockAllAttributesExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute | ConfigurationLockType.Exclude);
112                                 }
113
114                                 return lockAllAttributesExcept;
115                         }
116                 }
117
118                 ConfigurationLockCollection lockAllElementsExcept;
119                 public ConfigurationLockCollection LockAllElementsExcept {
120                         get {
121                                 if (lockAllElementsExcept == null) {
122                                         lockAllElementsExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Element | ConfigurationLockType.Exclude);
123                                 }
124
125                                 return lockAllElementsExcept;
126                         }
127                 }
128
129                 ConfigurationLockCollection lockAttributes;
130                 public ConfigurationLockCollection LockAttributes {
131                         get {
132                                 if (lockAttributes == null) {
133                                         lockAttributes = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute);
134                                 }
135
136                                 return lockAttributes;
137                         }
138                 }
139
140                 ConfigurationLockCollection lockElements;
141                 public ConfigurationLockCollection LockElements {
142                         get {
143                                 if (lockElements == null) {
144                                         lockElements = new ConfigurationLockCollection (this, ConfigurationLockType.Element);
145                                 }
146
147                                 return lockElements;
148                         }
149                 }
150
151                 bool lockItem;
152                 public bool LockItem {
153                         get { return lockItem; }
154                         set { lockItem = value; }
155                 }
156
157                 [MonoTODO]
158                 protected virtual void ListErrors (IList list)
159                 {
160                         throw new NotImplementedException ();
161                 }
162
163                 [MonoTODO]
164                 protected void SetPropertyValue (ConfigurationProperty prop, object value, bool ignoreLocks)
165                 {
166                         try {
167                                 if (value != null) {
168                                         /* XXX all i know for certain is that Validation happens here */
169                                         prop.Validate (value);
170
171                                         /* XXX presumably the actual setting of the
172                                          * property happens here instead of in the
173                                          * set_Item code below, but that would mean
174                                          * the Value needs to be stuffed in the
175                                          * property, not the propertyinfo (or else the
176                                          * property needs a ref to the property info
177                                          * to correctly set the value). */
178                                 }
179                         }
180                         catch (Exception e) {
181                                 throw new ConfigurationErrorsException (String.Format ("The value for the property '{0}' on type {1} is not valid.", prop.Name, this.ElementInformation.Type), e);
182                         }
183                 }
184
185                 internal ConfigurationPropertyCollection GetKeyProperties ()
186                 {
187                         if (keyProps != null) return keyProps;
188                         
189                         ConfigurationPropertyCollection tmpkeyProps = new ConfigurationPropertyCollection ();
190                                 foreach (ConfigurationProperty prop in Properties) {
191                                         if (prop.IsKey)
192                                         tmpkeyProps.Add (prop);
193                                 }
194
195                         return keyProps = tmpkeyProps;
196                 }
197
198                 internal ConfigurationElementCollection GetDefaultCollection ()
199                 {
200                         if (defaultCollection != null) return defaultCollection;
201
202                         ConfigurationProperty defaultCollectionProp = null;
203
204                         foreach (ConfigurationProperty prop in Properties) {
205                                 if (prop.IsDefaultCollection) {
206                                         defaultCollectionProp = prop;
207                                         break;
208                                 }
209                         }
210
211                         if (defaultCollectionProp != null) {
212                                 defaultCollection = this [defaultCollectionProp] as ConfigurationElementCollection;
213                         }
214
215                         return defaultCollection;
216                 }
217
218                 protected internal object this [ConfigurationProperty property] {
219                         get { return this [property.Name]; }
220                         set { this [property.Name] = value; }
221                 }
222
223                 protected internal object this [string property_name] {
224                         get {
225                                 PropertyInformation pi = ElementInformation.Properties [property_name];
226                                 if (pi == null)
227                                         throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
228
229                                 return pi.Value;
230                         }
231
232                         set {
233                                 PropertyInformation pi = ElementInformation.Properties [property_name];
234                                 if (pi == null)
235                                         throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
236
237                                 SetPropertyValue (pi.Property, value, false);
238
239                                 pi.Value = value;
240                                 modified = true;
241                         }
242                 }
243
244                 protected internal virtual ConfigurationPropertyCollection Properties {
245                         get {
246                                 if (map == null)
247                                         map = ElementMap.GetMap (GetType());
248                                 return map.Properties;
249                         }
250                 }
251
252                 public override bool Equals (object compareTo)
253                 {
254                         ConfigurationElement other = compareTo as ConfigurationElement;
255                         if (other == null) return false;
256                         if (GetType() != other.GetType()) return false;
257                         
258                         foreach (ConfigurationProperty prop in Properties) {
259                                 if (!object.Equals (this [prop], other [prop]))
260                                         return false;
261                         }
262                         return true;
263                 }
264
265                 public override int GetHashCode ()
266                 {
267                         int code = 0;
268                         object o;
269                         
270                         foreach (ConfigurationProperty prop in Properties) {
271                                 o = this [prop];
272                                 if (o == null)
273                                         continue;
274                                 
275                                 code += o.GetHashCode ();
276                         }
277                         
278                         return code;
279                 }
280
281                 internal virtual bool HasLocalModifications ()
282                 {
283                         foreach (PropertyInformation pi in ElementInformation.Properties)
284                                 if (pi.ValueOrigin == PropertyValueOrigin.SetHere && pi.IsModified)
285                                         return true;
286                         
287                         return false;
288                 }
289                 
290                 protected internal virtual void DeserializeElement (XmlReader reader, bool serializeCollectionKey)
291                 {
292                         Hashtable readProps = new Hashtable ();
293                         
294                         reader.MoveToContent ();
295                         elementPresent = true;
296                         
297                         while (reader.MoveToNextAttribute ())
298                         {
299                                 PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
300                                 if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
301                                         /* handle the built in ConfigurationElement attributes here */
302                                         if (reader.LocalName == "lockAllAttributesExcept") {
303                                                 LockAllAttributesExcept.SetFromList (reader.Value);
304                                         }
305                                         else if (reader.LocalName == "lockAllElementsExcept") {
306                                                 LockAllElementsExcept.SetFromList (reader.Value);
307                                         }
308                                         else if (reader.LocalName == "lockAttributes") {
309                                                 LockAttributes.SetFromList (reader.Value);
310                                         }
311                                         else if (reader.LocalName == "lockElements") {
312                                                 LockElements.SetFromList (reader.Value);
313                                         }
314                                         else if (reader.LocalName == "lockItem") {
315                                                 LockItem = (reader.Value.ToLowerInvariant () == "true");
316                                         }
317                                         else if (reader.LocalName == "xmlns") {
318                                                 /* ignore */
319                                         } else if (this is ConfigurationSection && reader.LocalName == "configSource") {
320                                                 /* ignore */
321                                         } else if (!OnDeserializeUnrecognizedAttribute (reader.LocalName, reader.Value))
322                                                 throw new ConfigurationErrorsException ("Unrecognized attribute '" + reader.LocalName + "'.", reader);
323
324                                         continue;
325                                 }
326                                 
327                                 if (readProps.ContainsKey (prop))
328                                         throw new ConfigurationErrorsException ("The attribute '" + prop.Name + "' may only appear once in this element.", reader);
329
330                                 string value = null;
331                                 try {
332                                         value = reader.Value;
333                                         ValidateValue (prop.Property, value);
334                                         prop.SetStringValue (value);
335                                 } catch (ConfigurationErrorsException) {
336                                         throw;
337                                 } catch (ConfigurationException) {
338                                         throw;
339                                 } catch (Exception ex) {
340                                         string msg = String.Format ("The value for the property '{0}' is not valid. The error is: {1}", prop.Name, ex.Message);
341                                         throw new ConfigurationErrorsException (msg, reader);
342                                 }
343                                 readProps [prop] = prop.Name;
344                         
345                                 ConfigXmlTextReader _reader = reader as ConfigXmlTextReader;
346                                 if (_reader != null){
347                                         prop.Source = _reader.Filename;
348                                         prop.LineNumber = _reader.LineNumber;
349                                 }
350                         }
351                         
352                         reader.MoveToElement ();
353                         if (reader.IsEmptyElement) {
354                                 reader.Skip ();
355                         } else {
356                                 int depth = reader.Depth;
357
358                                 reader.ReadStartElement ();
359                                 reader.MoveToContent ();
360
361                                 do {
362                                         if (reader.NodeType != XmlNodeType.Element) {
363                                                 reader.Skip ();
364                                                 continue;
365                                         }
366                                         
367                                         PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
368                                         if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
369                                                 if (!OnDeserializeUnrecognizedElement (reader.LocalName, reader)) {
370                                                         if (prop == null) {
371                                                                 ConfigurationElementCollection c = GetDefaultCollection ();
372                                                                 if (c != null && c.OnDeserializeUnrecognizedElement (reader.LocalName, reader))
373                                                                         continue;
374                                                         }
375                                                         throw new ConfigurationErrorsException ("Unrecognized element '" + reader.LocalName + "'.", reader);
376                                                 }
377                                                 continue;
378                                         }
379                                         
380                                         if (!prop.IsElement)
381                                                 throw new ConfigurationErrorsException ("Property '" + prop.Name + "' is not a ConfigurationElement.");
382                                         
383                                         if (readProps.Contains (prop))
384                                                 throw new ConfigurationErrorsException ("The element <" + prop.Name + "> may only appear once in this section.", reader);
385                                         
386                                         ConfigurationElement val = (ConfigurationElement) prop.Value;
387                                         val.DeserializeElement (reader, serializeCollectionKey);
388                                         readProps [prop] = prop.Name;
389
390                                         if(depth == reader.Depth)
391                                                 reader.Read();
392
393                                 } while (depth < reader.Depth);                         
394                         }
395                         
396                         modified = false;
397                                 
398                         foreach (PropertyInformation prop in ElementInformation.Properties)
399                                 if (!String.IsNullOrEmpty(prop.Name) && prop.IsRequired && !readProps.ContainsKey (prop)) {
400                                         PropertyInformation p = ElementInformation.Properties [prop.Name];
401                                         if (p == null) {
402                                                 object val = OnRequiredPropertyNotFound (prop.Name);
403                                                 if (!object.Equals (val, prop.DefaultValue)) {
404                                                         prop.Value = val;
405                                                         prop.IsModified = false;
406                                                 }
407                                         }
408                                 }
409
410                         PostDeserialize ();
411                 }
412
413                 protected virtual bool OnDeserializeUnrecognizedAttribute (string name, string value)
414                 {
415                         return false;
416                 }
417
418                 protected virtual bool OnDeserializeUnrecognizedElement (string element, XmlReader reader)
419                 {
420                         return false;
421                 }
422                 
423                 protected virtual object OnRequiredPropertyNotFound (string name)
424                 {
425                         throw new ConfigurationErrorsException ("Required attribute '" + name + "' not found.");
426                 }
427                 
428                 protected virtual void PreSerialize (XmlWriter writer)
429                 {
430                 }
431
432                 protected virtual void PostDeserialize ()
433                 {
434                 }
435
436                 protected internal virtual void InitializeDefault ()
437                 {
438                 }
439
440                 protected internal virtual bool IsModified ()
441                 {
442                         if (modified)
443                                 return true;
444
445                         foreach (PropertyInformation prop in ElementInformation.Properties) {
446                                 if (!prop.IsElement)
447                                         continue;
448                                 var element = prop.Value as ConfigurationElement;
449                                 if ((element == null) || !element.IsModified ())
450                                         continue;
451
452                                 modified = true;
453                                 break;
454                         }
455
456                         return modified;
457                 }
458                 
459                 protected internal virtual void SetReadOnly ()
460                 {
461                         readOnly = true;
462                 }
463                 
464                 public virtual bool IsReadOnly ()
465                 {
466                         return readOnly;
467                 }
468
469                 protected internal virtual void Reset (ConfigurationElement parentElement)
470                 {
471                         elementPresent = false;
472
473                         if (parentElement != null)
474                                 ElementInformation.Reset (parentElement.ElementInformation);
475                         else
476                                 InitializeDefault ();
477                 }
478
479                 protected internal virtual void ResetModified ()
480                 {
481                         modified = false;
482
483                         foreach (PropertyInformation p in ElementInformation.Properties) {
484                                 p.IsModified = false;
485
486                                 var element = p.Value as ConfigurationElement;
487                                 if (element != null)
488                                         element.ResetModified ();
489                         }
490                 }
491
492                 protected internal virtual bool SerializeElement (XmlWriter writer, bool serializeCollectionKey)
493                 {
494                         PreSerialize (writer);
495                         
496                         if (serializeCollectionKey) {
497                                 ConfigurationPropertyCollection props = GetKeyProperties ();
498                                 foreach (ConfigurationProperty prop in props)
499                                         writer.WriteAttributeString (prop.Name, prop.ConvertToString (this[prop.Name]));
500                                 return props.Count > 0;
501                         }
502                         
503                         bool wroteData = false;
504                         
505                         foreach (PropertyInformation prop in ElementInformation.Properties)
506                         {
507                                 if (prop.IsElement)
508                                         continue;
509
510                                 if (saveContext == null)
511                                         throw new InvalidOperationException ();
512                                 if (!saveContext.HasValue (prop))
513                                         continue;
514
515                                 writer.WriteAttributeString (prop.Name, prop.GetStringValue ());
516                                 wroteData = true;
517                         }
518                         
519                         foreach (PropertyInformation prop in ElementInformation.Properties)
520                         {
521                                 if (!prop.IsElement)
522                                         continue;
523                                 
524                                 ConfigurationElement val = (ConfigurationElement) prop.Value;
525                                 if (val != null)
526                                         wroteData = val.SerializeToXmlElement (writer, prop.Name) || wroteData;
527                         }
528                         return wroteData;
529                 }
530
531                 protected internal virtual bool SerializeToXmlElement (
532                                 XmlWriter writer, string elementName)
533                 {
534                         if (saveContext == null)
535                                 throw new InvalidOperationException ();
536                         if (!saveContext.HasValues ())
537                                 return false;
538
539                         if (elementName != null && elementName != "")
540                                 writer.WriteStartElement (elementName);
541                         bool res = SerializeElement (writer, false);
542                         if (elementName != null && elementName != "")
543                                 writer.WriteEndElement ();
544                         return res;
545                 }
546
547                 protected internal virtual void Unmerge (
548                                 ConfigurationElement source, ConfigurationElement parent,
549                                 ConfigurationSaveMode updateMode)
550                 {
551                         if (parent != null && source.GetType() != parent.GetType())
552                                 throw new ConfigurationErrorsException ("Can't unmerge two elements of different type");
553
554                         bool isMinimalOrModified = updateMode == ConfigurationSaveMode.Minimal ||
555                                 updateMode == ConfigurationSaveMode.Modified;
556
557                         foreach (PropertyInformation prop in source.ElementInformation.Properties)
558                         {
559                                 if (prop.ValueOrigin == PropertyValueOrigin.Default)
560                                         continue;
561                                 
562                                 PropertyInformation unmergedProp = ElementInformation.Properties [prop.Name];
563                                 
564                                 object sourceValue = prop.Value;
565                                 if (parent == null || !parent.HasValue (prop.Name)) {
566                                         unmergedProp.Value = sourceValue;
567                                         continue;
568                                 }
569
570                                 if (sourceValue == null)
571                                         continue;
572
573                                 object parentValue = parent [prop.Name];
574                                 if (!prop.IsElement) {
575                                         if (!object.Equals (sourceValue, parentValue) || 
576                                             (updateMode == ConfigurationSaveMode.Full) ||
577                                             (updateMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
578                                                 unmergedProp.Value = sourceValue;
579                                         continue;
580                                 }
581
582                                 var sourceElement = (ConfigurationElement) sourceValue;
583                                 if (isMinimalOrModified && !sourceElement.IsModified ())
584                                         continue;
585                                 if (parentValue == null) {
586                                         unmergedProp.Value = sourceValue;
587                                         continue;
588                                 }
589
590                                 var parentElement = (ConfigurationElement) parentValue;
591                                 ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
592                                 copy.Unmerge (sourceElement, parentElement, updateMode);
593                         }
594                 }
595                 
596                 internal bool HasValue (string propName)
597                 {
598                         PropertyInformation info = ElementInformation.Properties [propName];
599                         return info != null && info.ValueOrigin != PropertyValueOrigin.Default;
600                 }
601                 
602                 internal bool IsReadFromConfig (string propName)
603                 {
604                         PropertyInformation info = ElementInformation.Properties [propName];
605                         return info != null && info.ValueOrigin == PropertyValueOrigin.SetHere;
606                 }
607
608                 internal bool IsElementPresent
609                 {
610                         get {   return elementPresent;  }
611                 }
612
613                 void ValidateValue (ConfigurationProperty p, string value)
614                 {
615                         ConfigurationValidatorBase validator;
616                         if (p == null || (validator = p.Validator) == null)
617                                 return;
618                         
619                         if (!validator.CanValidate (p.Type))
620                                 throw new ConfigurationErrorsException (
621                                         String.Format ("Validator does not support type {0}", p.Type));
622                         validator.Validate (p.ConvertFromString (value));
623                 }
624
625                 /*
626                  * FIXME: LAMESPEC
627                  * 
628                  * SerializeElement() and SerializeToXmlElement() need to emit different output
629                  * based on the ConfigurationSaveMode that's being used.  Unfortunately, neither
630                  * of these methods take it as an argument and there seems to be no documented way
631                  * how to get it.
632                  * 
633                  * The parent element is needed because the element could be set to a different
634                  * than the default value in a parent configuration file, then set locally to that
635                  * same value.  This makes the element appear locally modified (so it's included
636                  * with ConfigurationSaveMode.Modified), but it should not be emitted with
637                  * ConfigurationSaveMode.Minimal.
638                  * 
639                  * In theory, we could save it into some private field in Unmerge(), but the
640                  * problem is that Unmerge() is kinda expensive and we also need a way of
641                  * determining whether or not the configuration has changed in Configuration.Save(),
642                  * prior to opening the output file for writing.
643                  * 
644                  * There are two places from where HasValues() is called:
645                  * a) From Configuration.Save() / SaveAs() to check whether the configuration needs
646                  *    to be saved.  This check is done prior to opening the file for writing.
647                  * b) From SerializeToXmlElement() to check whether to emit the element, using the
648                  *    parent and mode values from the cached 'SaveContext'.
649                  * 
650                  */
651
652                 /*
653                  * Check whether property 'prop' should be included in the serialized XML
654                  * based on the current ConfigurationSaveMode.
655                  */
656                 internal bool HasValue (ConfigurationElement parent, PropertyInformation prop,
657                                         ConfigurationSaveMode mode)
658                 {
659                         if (prop.ValueOrigin == PropertyValueOrigin.Default)
660                                 return false;
661                         
662                         if (mode == ConfigurationSaveMode.Modified &&
663                             prop.ValueOrigin == PropertyValueOrigin.SetHere && prop.IsModified) {
664                                 // Value has been modified locally, so we always emit it
665                                 // with ConfigurationSaveMode.Modified.
666                                 return true;
667                         }
668
669                         /*
670                          * Ok, now we have to check whether we're different from the inherited
671                          * value - which could either be a value that's set in a parent
672                          * configuration file or the default value.
673                          */
674                         
675                         var hasParentValue = parent != null && parent.HasValue (prop.Name);
676                         var parentOrDefault = hasParentValue ? parent [prop.Name] : prop.DefaultValue;
677
678                         if (!prop.IsElement)
679                                 return !object.Equals (prop.Value, parentOrDefault);
680
681                         /*
682                          * Ok, it's an element that has been set in a parent configuration file.                         * 
683                          * Recursively call HasValues() to check whether it's been locally modified.
684                          */
685                         var element = (ConfigurationElement) prop.Value;
686                         var parentElement = (ConfigurationElement) parentOrDefault;
687                         
688                         return element.HasValues (parentElement, mode);
689                 }
690
691                 /*
692                  * Check whether this element should be included in the serialized XML
693                  * based on the current ConfigurationSaveMode.
694                  * 
695                  * The 'parent' value is needed to determine whether the element currently
696                  * has a different value from what's been set in the parent configuration
697                  * hierarchy.
698                  */
699                 internal virtual bool HasValues (ConfigurationElement parent, ConfigurationSaveMode mode)
700                 {
701                         if (mode == ConfigurationSaveMode.Full)
702                                 return true;
703                         if (modified && (mode == ConfigurationSaveMode.Modified))
704                                 return true;
705                         
706                         foreach (PropertyInformation prop in ElementInformation.Properties) {
707                                 if (HasValue (parent, prop, mode))
708                                         return true;
709                         }
710                         
711                         return false;
712                 }
713
714                 /*
715                  * Cache the current 'parent' and 'mode' values for later use in SerializeToXmlElement()
716                  * and SerializeElement().
717                  * 
718                  * Make sure to call base when overriding this in a derived class.
719                  */
720                 internal virtual void PrepareSave (ConfigurationElement parent, ConfigurationSaveMode mode)
721                 {
722                         saveContext = new SaveContext (this, parent, mode);
723
724                         foreach (PropertyInformation prop in ElementInformation.Properties)
725                         {
726                                 if (!prop.IsElement)
727                                         continue;
728
729                                 var elem = (ConfigurationElement)prop.Value;
730                                 if (parent == null || !parent.HasValue (prop.Name))
731                                         elem.PrepareSave (null, mode);
732                                 else {
733                                         var parentValue = (ConfigurationElement)parent [prop.Name];
734                                         elem.PrepareSave (parentValue, mode);
735                                 }
736                         }
737                 }
738
739                 SaveContext saveContext;
740
741                 class SaveContext {
742                         public readonly ConfigurationElement Element;
743                         public readonly ConfigurationElement Parent;
744                         public readonly ConfigurationSaveMode Mode;
745
746                         public SaveContext (ConfigurationElement element, ConfigurationElement parent,
747                                             ConfigurationSaveMode mode)
748                         {
749                                 this.Element = element;
750                                 this.Parent = parent;
751                                 this.Mode = mode;
752                         }
753
754                         public bool HasValues ()
755                         {
756                                 if (Mode == ConfigurationSaveMode.Full)
757                                         return true;
758                                 return Element.HasValues (Parent, Mode);
759                         }
760
761                         public bool HasValue (PropertyInformation prop)
762                         {
763                                 if (Mode == ConfigurationSaveMode.Full)
764                                         return true;
765                                 return Element.HasValue (Parent, prop, Mode);
766                         }
767                 }
768         }
769         
770         internal class ElementMap
771         {
772                 static readonly Hashtable elementMaps = Hashtable.Synchronized (new Hashtable ());
773
774                 readonly ConfigurationPropertyCollection properties;
775                 readonly ConfigurationCollectionAttribute collectionAttribute;
776
777                 public static ElementMap GetMap (Type t)
778                 {
779                         ElementMap map = elementMaps [t] as ElementMap;
780                         if (map != null) return map;
781                         map = new ElementMap (t);
782                         elementMaps [t] = map;
783                         return map;
784                 }
785                 
786                 public ElementMap (Type t)
787                 {
788                         properties = new ConfigurationPropertyCollection ();
789                 
790                         collectionAttribute = Attribute.GetCustomAttribute (t, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
791                         
792                         PropertyInfo[] props = t.GetProperties (BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance);
793                         foreach (PropertyInfo prop in props)
794                         {
795                                 ConfigurationPropertyAttribute at = Attribute.GetCustomAttribute (prop, typeof(ConfigurationPropertyAttribute)) as ConfigurationPropertyAttribute;
796                                 if (at == null) continue;
797                                 string name = at.Name != null ? at.Name : prop.Name;
798
799                                 ConfigurationValidatorAttribute validatorAttr = Attribute.GetCustomAttribute (prop, typeof (ConfigurationValidatorAttribute)) as ConfigurationValidatorAttribute;
800                                 ConfigurationValidatorBase validator = validatorAttr != null ? validatorAttr.ValidatorInstance : null;
801
802                                 TypeConverterAttribute convertAttr = (TypeConverterAttribute) Attribute.GetCustomAttribute (prop, typeof (TypeConverterAttribute));
803                                 TypeConverter converter = convertAttr != null ? (TypeConverter) Activator.CreateInstance (Type.GetType (convertAttr.ConverterTypeName), true) : null;
804                                 ConfigurationProperty cp = new ConfigurationProperty (name, prop.PropertyType, at.DefaultValue, converter, validator, at.Options);
805
806                                 cp.CollectionAttribute = Attribute.GetCustomAttribute (prop, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;                             
807                                 properties.Add (cp);
808                         }
809                 }
810
811                 public ConfigurationCollectionAttribute CollectionAttribute
812                 {
813                         get { return collectionAttribute; }
814                 }
815                 
816                 public bool HasProperties
817                 {
818                         get { return properties.Count > 0; }
819                 }
820                 
821                 public ConfigurationPropertyCollection Properties
822                 {
823                         get {
824                                 return properties;
825                         }
826                 }
827         }
828 }
829