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 
ConfigurationElement()58 		protected ConfigurationElement ()
59 		{
60 		}
61 
InitFromProperty(PropertyInformation propertyInfo)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 
Init()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]
ListErrors(IList errorList)158 		protected virtual void ListErrors (IList errorList)
159 		{
160 			throw new NotImplementedException ();
161 		}
162 
163 		[MonoTODO]
SetPropertyValue(ConfigurationProperty prop, object value, bool ignoreLocks)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 
GetKeyProperties()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 
GetDefaultCollection()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 prop] {
219 			get { return this [prop.Name]; }
220 			set { this [prop.Name] = value; }
221 		}
222 
223 		protected internal object this [string propertyName] {
224 			get {
225 				PropertyInformation pi = ElementInformation.Properties [propertyName];
226 				if (pi == null)
227 					throw new InvalidOperationException ("Property '" + propertyName + "' not found in configuration element");
228 
229 				return pi.Value;
230 			}
231 
232 			set {
233 				PropertyInformation pi = ElementInformation.Properties [propertyName];
234 				if (pi == null)
235 					throw new InvalidOperationException ("Property '" + propertyName + "' 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 
Equals(object compareTo)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 
GetHashCode()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 
HasLocalModifications()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 
DeserializeElement(XmlReader reader, bool serializeCollectionKey)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 
OnDeserializeUnrecognizedAttribute(string name, string value)413 		protected virtual bool OnDeserializeUnrecognizedAttribute (string name, string value)
414 		{
415 			return false;
416 		}
417 
OnDeserializeUnrecognizedElement(string elementName, XmlReader reader)418 		protected virtual bool OnDeserializeUnrecognizedElement (string elementName, XmlReader reader)
419 		{
420 			return false;
421 		}
422 
OnRequiredPropertyNotFound(string name)423 		protected virtual object OnRequiredPropertyNotFound (string name)
424 		{
425 			throw new ConfigurationErrorsException ("Required attribute '" + name + "' not found.");
426 		}
427 
PreSerialize(XmlWriter writer)428 		protected virtual void PreSerialize (XmlWriter writer)
429 		{
430 		}
431 
PostDeserialize()432 		protected virtual void PostDeserialize ()
433 		{
434 		}
435 
InitializeDefault()436 		protected internal virtual void InitializeDefault ()
437 		{
438 		}
439 
IsModified()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 
SetReadOnly()459 		protected internal virtual void SetReadOnly ()
460 		{
461 			readOnly = true;
462 		}
463 
IsReadOnly()464 		public virtual bool IsReadOnly ()
465 		{
466 			return readOnly;
467 		}
468 
Reset(ConfigurationElement parentElement)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 
ResetModified()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 
SerializeElement(XmlWriter writer, bool serializeCollectionKey)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 
SerializeToXmlElement( XmlWriter writer, string elementName)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 
Unmerge( ConfigurationElement sourceElement, ConfigurationElement parentElement, ConfigurationSaveMode saveMode)547 		protected internal virtual void Unmerge (
548 				ConfigurationElement sourceElement, ConfigurationElement parentElement,
549 				ConfigurationSaveMode saveMode)
550 		{
551 			if (parentElement != null && sourceElement.GetType() != parentElement.GetType())
552 				throw new ConfigurationErrorsException ("Can't unmerge two elements of different type");
553 
554 			bool isMinimalOrModified = saveMode == ConfigurationSaveMode.Minimal ||
555 				saveMode == ConfigurationSaveMode.Modified;
556 
557 			foreach (PropertyInformation prop in sourceElement.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 (parentElement == null || !parentElement.HasValue (prop.Name)) {
566 					unmergedProp.Value = sourceValue;
567 					continue;
568 				}
569 
570 				if (sourceValue == null)
571 					continue;
572 
573 				object parentValue = parentElement [prop.Name];
574 				if (!prop.IsElement) {
575 					if (!object.Equals (sourceValue, parentValue) ||
576 					    (saveMode == ConfigurationSaveMode.Full) ||
577 					    (saveMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
578 						unmergedProp.Value = sourceValue;
579 					continue;
580 				}
581 
582 				var sourceElementValue = (ConfigurationElement) sourceValue;
583 				if (isMinimalOrModified && !sourceElementValue.IsModified ())
584 					continue;
585 				if (parentValue == null) {
586 					unmergedProp.Value = sourceValue;
587 					continue;
588 				}
589 
590 				var parentElementValue = (ConfigurationElement) parentValue;
591 				ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
592 				copy.Unmerge (sourceElementValue, parentElementValue, saveMode);
593 			}
594 		}
595 
HasValue(string propName)596 		internal bool HasValue (string propName)
597 		{
598 			PropertyInformation info = ElementInformation.Properties [propName];
599 			return info != null && info.ValueOrigin != PropertyValueOrigin.Default;
600 		}
601 
IsReadFromConfig(string propName)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 
ValidateValue(ConfigurationProperty p, string value)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 		 */
HasValue(ConfigurationElement parent, PropertyInformation prop, ConfigurationSaveMode mode)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 		 */
HasValues(ConfigurationElement parent, ConfigurationSaveMode mode)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 		 */
PrepareSave(ConfigurationElement parent, ConfigurationSaveMode mode)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 
SaveContext(ConfigurationElement element, ConfigurationElement parent, ConfigurationSaveMode mode)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 
HasValues()754 			public bool HasValues ()
755 			{
756 				if (Mode == ConfigurationSaveMode.Full)
757 					return true;
758 				return Element.HasValues (Parent, Mode);
759 			}
760 
HasValue(PropertyInformation prop)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 
GetMap(Type t)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 
ElementMap(Type t)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 
830