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