2005-11-10 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                 public 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                         throw new NotImplementedException ();
145                 }
146
147                 internal ConfigurationPropertyCollection GetKeyProperties ()
148                 {
149                         if (keyProps != null) return keyProps;
150                         
151                         if (map != null && map.Properties == Properties)
152                                 keyProps = map.KeyProperties;
153                         else {
154                                 keyProps = new ConfigurationPropertyCollection ();
155                                 foreach (ConfigurationProperty prop in Properties) {
156                                         if (prop.IsKey)
157                                                 keyProps.Add (prop);
158                                 }
159                         }
160                         return keyProps;
161                 }
162
163                 internal ConfigurationElementCollection GetDefaultCollection ()
164                 {
165                         if (defaultCollection != null) return defaultCollection;
166
167                         ConfigurationProperty defaultCollectionProp = null;
168
169                         if (map != null && map.Properties == Properties) {
170                                 defaultCollectionProp = map.DefaultCollectionProperty;
171                         }
172                         else {
173                                 foreach (ConfigurationProperty prop in Properties) {
174                                         if (prop.IsDefaultCollection) {
175                                                 defaultCollectionProp = prop;
176                                                 break;
177                                         }
178                                 }
179                         }
180
181                         if (defaultCollectionProp != null) {
182                                 defaultCollection = this [defaultCollectionProp] as ConfigurationElementCollection;
183                         }
184
185                         return defaultCollection;
186                 }
187
188                 protected internal object this [ConfigurationProperty property] {
189                         get { return this [property.Name]; }
190                         set { this [property.Name] = value; }
191                 }
192
193                 protected internal object this [string property_name] {
194                         get {
195                                 PropertyInformation pi = ElementInformation.Properties [property_name];
196                                 if (pi == null)
197                                         throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
198
199                                 return pi.Value;
200                         }
201
202                         set {
203                                 PropertyInformation pi = ElementInformation.Properties [property_name];
204                                 if (pi == null)
205                                         throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
206                                 
207                                 pi.Value = value;
208                                 modified = true;
209                         }
210                 }
211
212                 protected internal virtual ConfigurationPropertyCollection Properties {
213                         get {
214                                 if (map == null)
215                                         map = ElementMap.GetMap (GetType());
216                                 return map.Properties;
217                         }
218                 }
219
220                 public override bool Equals (object compareTo)
221                 {
222                         ConfigurationElement other = compareTo as ConfigurationElement;
223                         if (other == null) return false;
224                         if (GetType() != other.GetType()) return false;
225                         
226                         foreach (ConfigurationProperty prop in Properties) {
227                                 if (!object.Equals (this [prop], other [prop]))
228                                         return false;
229                         }
230                         return true;
231                 }
232
233                 public override int GetHashCode ()
234                 {
235                         int code = 0;
236                         foreach (ConfigurationProperty prop in Properties)
237                                 code += this [prop].GetHashCode ();
238                         return code;
239                 }
240
241                 internal virtual bool HasValues ()
242                 {
243                         foreach (PropertyInformation pi in ElementInformation.Properties)
244                                 if (pi.ValueOrigin != PropertyValueOrigin.Default)
245                                         return true;
246                         return false;
247                 }
248                 
249                 protected internal virtual void DeserializeElement (XmlReader reader, bool serializeCollectionKey)
250                 {
251                         Hashtable readProps = new Hashtable ();
252                         
253                         reader.MoveToContent ();
254                         while (reader.MoveToNextAttribute ())
255                         {
256                                 PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
257                                 if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
258                                         if (!OnDeserializeUnrecognizedAttribute (reader.LocalName, reader.Value))
259                                                 throw new ConfigurationException ("Unrecognized attribute '" + reader.LocalName + "'.");
260                                         continue;
261                                 }
262                                 
263                                 if (readProps.ContainsKey (prop))
264                                         throw new ConfigurationException ("The attribute '" + prop.Name + "' may only appear once in this element.");
265                                 
266                                 prop.SetStringValue (reader.Value);
267                                 readProps [prop] = prop.Name;
268                         }
269                         
270                         reader.MoveToElement ();
271                         if (reader.IsEmptyElement) {
272                                 reader.Skip ();
273                         }
274                         else {
275
276                                 int depth = reader.Depth;
277
278                                 reader.ReadStartElement ();
279                                 reader.MoveToContent ();
280
281                                 do {
282                                         if (reader.NodeType != XmlNodeType.Element) {
283                                                 reader.Skip ();
284                                                 continue;
285                                         }
286                                         
287                                         PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
288                                         if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
289                                                 if (prop == null) {
290                                                         ConfigurationElementCollection c = GetDefaultCollection ();
291                                                         if (c != null && c.OnDeserializeUnrecognizedElement (reader.LocalName, reader))
292                                                                 continue;
293                                                 }
294
295                                                 if (!OnDeserializeUnrecognizedElement (reader.LocalName, reader)) {
296                                                         throw new ConfigurationException ("Unrecognized element '" + reader.LocalName + "'.");
297                                                 }
298                                                 continue;
299                                         }
300                                         
301                                         if (!prop.IsElement)
302                                                 throw new ConfigurationException ("Property '" + prop.Name + "' is not a ConfigurationElement.");
303                                         
304                                         if (readProps.Contains (prop))
305                                                 throw new ConfigurationException ("The element <" + prop.Name + "> may only appear once in this section.");
306                                         
307                                         ConfigurationElement val = (ConfigurationElement) prop.Value;
308                                         val.DeserializeElement (reader, serializeCollectionKey);
309                                         readProps [prop] = prop.Name;
310
311                                         reader.Read();
312
313                                 } while (depth < reader.Depth);
314
315                                 if (reader.NodeType == XmlNodeType.EndElement)
316                                         reader.Read ();
317                         }
318                         
319                         modified = false;
320                                 
321                         foreach (PropertyInformation prop in ElementInformation.Properties)
322                                 if (prop.IsRequired && !readProps.ContainsKey (prop)) {
323                                         object val = OnRequiredPropertyNotFound (prop.Name);
324                                         if (!object.Equals (val, prop.DefaultValue)) {
325                                                 prop.Value = val;
326                                                 prop.IsModified = false;
327                                         }
328                                 }
329
330                         PostDeserialize ();
331                 }
332
333                 protected virtual bool OnDeserializeUnrecognizedAttribute (string name, string value)
334                 {
335                         return false;
336                 }
337
338                 protected virtual bool OnDeserializeUnrecognizedElement (string element, XmlReader reader)
339                 {
340                         return false;
341                 }
342                 
343                 protected virtual object OnRequiredPropertyNotFound (string name)
344                 {
345                         throw new ConfigurationErrorsException ("Required attribute '" + name + "' not found.");
346                 }
347                 
348                 protected virtual void PreSerialize (XmlWriter writer)
349                 {
350                 }
351
352                 protected virtual void PostDeserialize ()
353                 {
354                 }
355
356                 protected internal virtual void InitializeDefault ()
357                 {
358                 }
359
360                 protected internal virtual bool IsModified ()
361                 {
362                         return modified;
363                 }
364                 
365                 protected internal virtual void SetReadOnly ()
366                 {
367                         readOnly = true;
368                 }
369                 
370                 public virtual bool IsReadOnly ()
371                 {
372                         return readOnly;
373                 }
374
375                 protected internal virtual void Reset (ConfigurationElement parentElement)
376                 {
377                         if (parentElement != null)
378                                 ElementInformation.Reset (parentElement.ElementInformation);
379                         else
380                                 InitializeDefault ();
381                 }
382
383                 protected internal virtual void ResetModified ()
384                 {
385                         modified = false;
386                         foreach (PropertyInformation p in ElementInformation.Properties)
387                                 p.IsModified = false;
388                 }
389
390                 protected internal virtual bool SerializeElement (XmlWriter writer, bool serializeCollectionKey)
391                 {
392                         PreSerialize (writer);
393                         
394                         if (serializeCollectionKey) {
395                                 ConfigurationPropertyCollection props = GetKeyProperties ();
396                                 foreach (ConfigurationProperty prop in props)
397                                         writer.WriteAttributeString (prop.Name, prop.ConvertToString (this[prop.Name]));
398                                 return props.Count > 0;
399                         }
400                         
401                         bool wroteData = false;
402                         
403                         foreach (PropertyInformation prop in ElementInformation.Properties)
404                         {
405                                 if (prop.IsElement || prop.ValueOrigin == PropertyValueOrigin.Default)
406                                         continue;
407                                 
408                                 if (!object.Equals (prop.Value, prop.DefaultValue)) {
409                                         writer.WriteAttributeString (prop.Name, prop.GetStringValue ());
410                                         wroteData = true;
411                                 }
412                         }
413                         
414                         foreach (PropertyInformation prop in ElementInformation.Properties)
415                         {
416                                 if (!prop.IsElement)
417                                         continue;
418                                 
419                                 ConfigurationElement val = (ConfigurationElement) prop.Value;
420                                 if (val != null && val.HasValues ()) {
421                                         wroteData = val.SerializeToXmlElement (writer, prop.Name) || wroteData;
422                                 }
423                         }
424                         return wroteData;
425                 }
426                                 
427                 protected internal virtual bool SerializeToXmlElement (
428                                 XmlWriter writer, string elementName)
429                 {
430                         writer.WriteStartElement (elementName);
431                         bool res = SerializeElement (writer, false);
432                         writer.WriteEndElement ();
433                         return res;
434                 }
435
436                 protected internal virtual void Unmerge (
437                                 ConfigurationElement source, ConfigurationElement parent,
438                                 ConfigurationSaveMode updateMode)
439                 {
440                         if (parent != null && source.GetType() != parent.GetType())
441                                 throw new ConfigurationException ("Can't unmerge two elements of different type");
442                         
443                         foreach (PropertyInformation prop in source.ElementInformation.Properties)
444                         {
445                                 if (prop.ValueOrigin == PropertyValueOrigin.Default)
446                                         continue;
447                                 
448                                 PropertyInformation unmergedProp = ElementInformation.Properties [prop.Name];
449                                 
450                                 object sourceValue = prop.Value;
451                                 if      (parent == null || !parent.HasValue (prop.Name)) {
452                                         unmergedProp.Value = sourceValue;
453                                         continue;
454                                 }
455                                 else if (sourceValue != null) {
456                                         object parentValue = parent [prop.Name];
457                                         if (prop.IsElement) {
458                                                 if (parentValue != null) {
459                                                         ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
460                                                         copy.Unmerge ((ConfigurationElement) sourceValue, (ConfigurationElement) parentValue, updateMode);
461                                                 }
462                                                 else
463                                                         unmergedProp.Value = sourceValue;
464                                         }
465                                         else {
466                                                 if (!object.Equals (sourceValue, parentValue) || 
467                                                         (updateMode == ConfigurationSaveMode.Full) ||
468                                                         (updateMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
469                                                         unmergedProp.Value = sourceValue;
470                                         }
471                                 }
472                         }
473                 }
474                 
475                 internal bool HasValue (string propName)
476                 {
477                         PropertyInformation info = ElementInformation.Properties [propName];
478                         return info != null && info.ValueOrigin != PropertyValueOrigin.Default;
479                 }
480                 
481                 internal bool IsReadFromConfig (string propName)
482                 {
483                         PropertyInformation info = ElementInformation.Properties [propName];
484                         return info != null && info.ValueOrigin == PropertyValueOrigin.SetHere;
485                 }
486         }
487         
488         internal class ElementMap
489         {
490                 static Hashtable elementMaps = new Hashtable ();
491                 
492                 ConfigurationPropertyCollection properties;
493                 ConfigurationPropertyCollection keyProperties;
494                 ConfigurationProperty defaultCollectionProperty;
495
496                 ConfigurationCollectionAttribute collectionAttribute;
497                 
498                 public static ElementMap GetMap (Type t)
499                 {
500                         lock (elementMaps) {
501                                 ElementMap map = elementMaps [t] as ElementMap;
502                                 if (map != null) return map;
503                                 map = new ElementMap (t);
504                                 elementMaps [t] = map;
505                                 return map;
506                         }
507                 }
508                 
509                 public ElementMap (Type t)
510                 {
511                         ReflectProperties (t);
512                 }
513                 
514                 protected void ReflectProperties (Type t)
515                 {
516                         collectionAttribute = Attribute.GetCustomAttribute (t, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
517                         
518                         PropertyInfo[] props = t.GetProperties ();
519                         foreach (PropertyInfo prop in props)
520                         {
521                                 ConfigurationPropertyAttribute at = Attribute.GetCustomAttribute (prop, typeof(ConfigurationPropertyAttribute)) as ConfigurationPropertyAttribute;
522                                 if (at == null) continue;
523                                 string name = at.Name != null ? at.Name : prop.Name;
524
525                                 if (
526                                     /* if we have no default value, don't bother to check further */
527                                     at.DefaultValue != null && at.DefaultValue != ConfigurationProperty.NoDefaultValue
528                                     )
529                                 {
530                                         try {
531                                                 Convert.ChangeType (at.DefaultValue, prop.PropertyType);
532                                         }
533                                         catch {
534                                                 throw new ConfigurationErrorsException (String.Format ("The default value for property '{0}' has a different type than the one of the property itself",
535                                                                                                        name));
536                                         }
537                                 }
538
539                                 ConfigurationValidatorAttribute validatorAttr = Attribute.GetCustomAttribute (t, typeof(ConfigurationValidatorAttribute)) as ConfigurationValidatorAttribute;
540                                 ConfigurationValidatorBase validator = validatorAttr != null ? validatorAttr.ValidatorInstance : new DefaultValidator();
541                                 
542                                 TypeConverter converter = TypeDescriptor.GetConverter (prop.PropertyType);
543                                 ConfigurationProperty cp = new ConfigurationProperty (name, prop.PropertyType, at.DefaultValue, converter, validator, at.Options);
544                                 
545                                 cp.CollectionAttribute = Attribute.GetCustomAttribute (prop, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
546                                 
547                                 if (properties == null) properties = new ConfigurationPropertyCollection ();
548                                 properties.Add (cp);
549                         }
550                 }
551                 
552                 public bool HasProperties
553                 {
554                         get { return properties != null && properties.Count > 0; }
555                 }
556                 
557                 public ConfigurationPropertyCollection Properties
558                 {
559                         get {
560                                 if (properties == null) properties = new ConfigurationPropertyCollection ();
561                                 return properties;
562                         }
563                 }
564                 
565                 public ConfigurationPropertyCollection KeyProperties {
566                         get {
567                                 if (keyProperties == null) {
568                                         keyProperties = new ConfigurationPropertyCollection ();
569                                         
570                                         if (properties != null)
571                                                 foreach (ConfigurationProperty p in properties)
572                                                         if (p.IsKey) keyProperties.Add (p);
573                                 }
574                                 return keyProperties;
575                         }
576                 }
577                 
578                 public ConfigurationCollectionAttribute CollectionAttribute {
579                         get { return collectionAttribute; }
580                 }
581                 
582                 public ConfigurationProperty DefaultCollectionProperty {
583                         get {
584                                 if (defaultCollectionProperty == null) {
585                                         if (properties != null)
586                                                 foreach (ConfigurationProperty p in properties) {
587                                                         if (p.IsDefaultCollection) defaultCollectionProperty = p;
588                                                         break;
589                                                 }
590                                 }
591                                 return defaultCollectionProperty;
592                         }
593                 }
594         }
595 }
596
597 #endif