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