Fix .NET 1.1 compatability
[mono.git] / mcs / class / System / System.Collections.Specialized / NameObjectCollectionBase.cs
1 //
2 // System.Collections.Specialized.NameObjectCollectionBase.cs
3 //
4 // Author:
5 //   Gleb Novodran
6 //   Andreas Nahr (ClassDevelopment@A-SoftTech.com)
7 //
8 // (C) Ximian, Inc.  http://www.ximian.com
9 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Collections;
33 using System.Runtime.Serialization;
34
35 namespace System.Collections.Specialized
36 {
37         [Serializable]
38         public abstract class NameObjectCollectionBase : ICollection, IEnumerable, ISerializable, IDeserializationCallback
39         {
40                 private Hashtable m_ItemsContainer;
41                 /// <summary>
42                 /// Extends Hashtable based Items container to support storing null-key pairs
43                 /// </summary>
44                 private _Item m_NullKeyItem;
45                 private ArrayList m_ItemsArray;
46                 private IHashCodeProvider m_hashprovider;
47                 private IComparer m_comparer;
48                 private int m_defCapacity;
49                 private bool m_readonly;
50                 SerializationInfo infoCopy;
51                 private KeysCollection keyscoll;
52 #if NET_2_0
53                 private IEqualityComparer equality_comparer;
54
55                 internal IEqualityComparer EqualityComparer {
56                         get { return equality_comparer; }
57                 }
58 #endif
59                 internal IComparer Comparer {
60                         get {return m_comparer;}
61                 }
62
63                 internal IHashCodeProvider HashCodeProvider {
64                         get {return m_hashprovider;}
65                 }
66
67                 internal class _Item
68                 {
69                         public string key;
70                         public object value;
71                         public _Item(string key, object value)
72                         {
73                                 this.key = key;
74                                 this.value = value;
75                         }
76                 }               
77                 /// <summary>
78                 /// Implements IEnumerable interface for KeysCollection
79                 /// </summary>
80                 [Serializable]
81                 internal class _KeysEnumerator : IEnumerator
82                 {
83                         private NameObjectCollectionBase m_collection;
84                         private int m_position;
85
86                         internal _KeysEnumerator(NameObjectCollectionBase collection)
87                         {
88                                 m_collection = collection;
89                                 Reset();
90                         }
91                         public object Current 
92                         {
93                                 
94                                 get{
95                                         if ((m_position<m_collection.Count)||(m_position<0))
96                                                 return m_collection.BaseGetKey(m_position);
97                                         else 
98                                                 throw new InvalidOperationException();
99                                 }
100                                 
101                         }
102                         public bool MoveNext()
103                         {
104                                 return ((++m_position)<m_collection.Count)?true:false;
105                         }
106                         public void Reset()
107                         {
108                                 m_position = -1;
109                         }
110                 }
111                 
112                 /// <summary>
113                 /// SDK: Represents a collection of the String keys of a collection.
114                 /// </summary>
115                 [Serializable]
116                 public class KeysCollection : ICollection, IEnumerable
117                 {
118                         private NameObjectCollectionBase m_collection;
119
120                         internal KeysCollection (NameObjectCollectionBase collection)
121                         {
122                                 this.m_collection = collection;
123                         }
124
125                         public virtual string Get( int index )
126                         {
127                                 return m_collection.BaseGetKey(index);
128                         }
129                         
130                         // ICollection methods -----------------------------------
131                         void ICollection.CopyTo(Array arr, int index)
132                         {
133                                 if (arr==null)
134                                         throw new ArgumentNullException("array can't be null");
135                                 IEnumerator en = this.GetEnumerator();
136                                 int i = index;
137                                 while (en.MoveNext())
138                                 {
139                                         arr.SetValue(en.Current,i);
140                                         i++;
141                                 }                       
142                         }
143
144                         bool ICollection.IsSynchronized
145                         {
146                                 get{
147                                         return false;
148                                 }
149                         }
150                         object ICollection.SyncRoot
151                         {
152                                 get{
153                                         return m_collection;
154                                 }
155                         }
156                         /// <summary>
157                         /// Gets the number of keys in the NameObjectCollectionBase.KeysCollection
158                         /// </summary>
159                         public int Count 
160                         {
161                                 get{
162                                         return m_collection.Count;
163                                 }
164                         }
165
166                         public string this [int index] {
167                                 get { return Get (index); }
168                         }
169
170                         // IEnumerable methods --------------------------------
171                         /// <summary>
172                         /// SDK: Returns an enumerator that can iterate through the NameObjectCollectionBase.KeysCollection.
173                         /// </summary>
174                         /// <returns></returns>
175                         public IEnumerator GetEnumerator()
176                         {
177                                 return new _KeysEnumerator(m_collection);
178                         }
179                 }
180
181                 //--------------- Protected Instance Constructors --------------
182                 
183                 /// <summary>
184                 /// SDK: Initializes a new instance of the NameObjectCollectionBase class that is empty.
185                 /// </summary>
186                 protected NameObjectCollectionBase ()
187                 {
188                         m_readonly = false;
189 #if NET_1_0
190                         m_hashprovider = CaseInsensitiveHashCodeProvider.Default;
191                         m_comparer = CaseInsensitiveComparer.Default;
192 #else
193                         m_hashprovider = CaseInsensitiveHashCodeProvider.DefaultInvariant;
194                         m_comparer = CaseInsensitiveComparer.DefaultInvariant;
195 #endif
196                         m_defCapacity = 0;
197                         Init();
198                 }
199                 
200                 protected NameObjectCollectionBase( int capacity )
201                 {
202                         m_readonly = false;
203 #if NET_1_0
204                         m_hashprovider = CaseInsensitiveHashCodeProvider.Default;
205                         m_comparer = CaseInsensitiveComparer.Default;
206 #else
207                         m_hashprovider = CaseInsensitiveHashCodeProvider.DefaultInvariant;
208                         m_comparer = CaseInsensitiveComparer.DefaultInvariant;
209 #endif
210                         m_defCapacity = capacity;
211                         Init();
212                 }               
213
214 #if NET_2_0
215
216                 internal NameObjectCollectionBase (IEqualityComparer equalityComparer, IComparer comparer, IHashCodeProvider hcp)
217                 {
218                         equality_comparer = equalityComparer;
219                         m_comparer = comparer;
220                         m_hashprovider = hcp;
221                         m_readonly = false;
222                         m_defCapacity = 0;
223                         Init ();
224                 }
225
226                 protected NameObjectCollectionBase (IEqualityComparer equalityComparer) : this( (equalityComparer == null ? StringComparer.InvariantCultureIgnoreCase : equalityComparer), null, null)
227                 {                       
228                 }               
229
230                 [Obsolete ("Use NameObjectCollectionBase(IEqualityComparer)")]
231 #endif
232                 protected NameObjectCollectionBase( IHashCodeProvider hashProvider, IComparer comparer )
233                 {                       
234                         m_comparer = comparer;
235                         m_hashprovider = hashProvider;
236                         m_readonly = false;
237                         m_defCapacity = 0;
238                         Init ();
239                 }
240
241                 protected NameObjectCollectionBase (SerializationInfo info, StreamingContext context)
242                 {
243                         infoCopy = info;
244                 }
245
246 #if NET_2_0
247                 protected NameObjectCollectionBase (int capacity, IEqualityComparer equalityComparer)
248                 {
249                         m_readonly = false;
250                         equality_comparer = (equalityComparer == null ? StringComparer.InvariantCultureIgnoreCase : equalityComparer);
251                         m_defCapacity = capacity;
252                         Init();
253                 }
254
255                 [Obsolete ("Use NameObjectCollectionBase(int,IEqualityComparer)")]
256 #endif
257                 protected NameObjectCollectionBase( int capacity, IHashCodeProvider hashProvider, IComparer comparer )
258                 {
259                         m_readonly = false;
260                         
261                         m_hashprovider = hashProvider;
262                         m_comparer = comparer;
263                         m_defCapacity = capacity;
264                         Init();
265                 }
266                 
267                 private void Init ()
268                 {
269 #if NET_2_0
270                         if (equality_comparer != null)
271                                 m_ItemsContainer = new Hashtable (m_defCapacity, equality_comparer);
272                         else
273                                 m_ItemsContainer = new Hashtable (m_defCapacity, m_hashprovider, m_comparer);
274 #else
275                         m_ItemsContainer = new Hashtable (m_defCapacity, m_hashprovider, m_comparer);
276 #endif
277                         m_ItemsArray = new ArrayList();
278                         m_NullKeyItem = null;   
279                 }
280
281                 //--------------- Public Instance Properties -------------------
282
283                 public virtual NameObjectCollectionBase.KeysCollection Keys {
284                         get {
285                                 if (keyscoll == null)
286                                         keyscoll = new KeysCollection (this);
287                                 return keyscoll;
288                         }
289                 }
290                                 
291                 //--------------- Public Instance Methods ----------------------
292                 // 
293                 /// <summary>
294                 /// SDK: Returns an enumerator that can iterate through the NameObjectCollectionBase.
295                 /// 
296                 /// <remark>This enumerator returns the keys of the collection as strings.</remark>
297                 /// </summary>
298                 /// <returns></returns>
299                 public
300 #if NET_2_0             
301                 virtual
302 #endif
303                 IEnumerator GetEnumerator()
304                 {
305                         return new _KeysEnumerator(this);
306                 }
307
308                 // ISerializable
309                 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
310                 {
311                         if (info == null)
312                                 throw new ArgumentNullException ("info");
313
314                         int count = Count;
315                         string [] keys = new string [count];
316                         object [] values = new object [count];
317                         int i = 0;
318                         foreach (_Item item in m_ItemsArray) {
319                                 keys [i] = item.key;
320                                 values [i] = item.value;
321                                 i++;
322                         }
323
324 #if NET_2_0
325                         if (equality_comparer != null) {
326                                 info.AddValue ("KeyComparer", equality_comparer, typeof (IEqualityComparer));
327                                 info.AddValue ("Version", 4, typeof (int));
328                         } else {
329                                 info.AddValue ("HashProvider", m_hashprovider, typeof (IHashCodeProvider));
330                                 info.AddValue ("Comparer", m_comparer, typeof (IComparer));
331                                 info.AddValue ("Version", 2, typeof (int));
332                         }
333 #else
334                         info.AddValue ("HashProvider", m_hashprovider, typeof (IHashCodeProvider));
335                         info.AddValue ("Comparer", m_comparer, typeof (IComparer));
336 #endif
337                         info.AddValue("ReadOnly", m_readonly);
338                         info.AddValue("Count", count);
339                         info.AddValue("Keys", keys, typeof(string[]));
340                         info.AddValue("Values", values, typeof(object[]));
341                 }
342
343                 // ICollection
344                 public virtual int Count 
345                 {
346                         get{
347                                 return m_ItemsArray.Count;
348                         }
349                 }
350
351                 bool ICollection.IsSynchronized
352                 {
353                         get { return false; }
354                 }
355
356                 object ICollection.SyncRoot
357                 {
358                         get { return this; }
359                 }
360
361                 void ICollection.CopyTo (Array array, int index)
362                 {
363                         (Keys as ICollection).CopyTo (array, index);
364                 }
365
366                 // IDeserializationCallback
367                 public virtual void OnDeserialization (object sender)
368                 {
369                         SerializationInfo info = infoCopy;
370                         
371                         // If a subclass overrides the serialization constructor
372                         // and inplements its own serialization process, infoCopy will
373                         // be null and we can ignore this callback.
374                         if (info == null)
375                                 return;
376
377                         infoCopy = null;
378                         m_hashprovider = (IHashCodeProvider) info.GetValue ("HashProvider",
379                                                                             typeof (IHashCodeProvider));
380 #if NET_2_0
381                         if (m_hashprovider == null) {
382                                 equality_comparer = (IEqualityComparer) info.GetValue ("KeyComparer", typeof (IEqualityComparer));
383                         } else {
384                                 m_comparer = (IComparer) info.GetValue ("Comparer", typeof (IComparer));
385                                 if (m_comparer == null)
386                                         throw new SerializationException ("The comparer is null");
387                         }
388 #else
389                         if (m_hashprovider == null)
390                                 throw new SerializationException ("The hash provider is null");
391
392                         m_comparer = (IComparer) info.GetValue ("Comparer", typeof (IComparer));
393                         if (m_comparer == null)
394                                 throw new SerializationException ("The comparer is null");
395 #endif
396                         m_readonly = info.GetBoolean ("ReadOnly");
397                         string [] keys = (string []) info.GetValue ("Keys", typeof (string []));
398                         if (keys == null)
399                                 throw new SerializationException ("keys is null");
400
401                         object [] values = (object []) info.GetValue ("Values", typeof (object []));
402                         if (values == null)
403                                 throw new SerializationException ("values is null");
404
405                         Init ();
406                         int count = keys.Length;
407                         for (int i = 0; i < count; i++)
408                                 BaseAdd (keys [i], values [i]);
409                 }
410
411                 //--------------- Protected Instance Properties ----------------
412                 /// <summary>
413                 /// SDK: Gets or sets a value indicating whether the NameObjectCollectionBase instance is read-only.
414                 /// </summary>
415                 protected bool IsReadOnly 
416                 {
417                         get{
418                                 return m_readonly;
419                         }
420                         set{
421                                 m_readonly=value;
422                         }
423                 }
424                 
425                 //--------------- Protected Instance Methods -------------------
426                 /// <summary>
427                 /// Adds an Item with the specified key and value into the <see cref="NameObjectCollectionBase"/>NameObjectCollectionBase instance.
428                 /// </summary>
429                 /// <param name="name"></param>
430                 /// <param name="value"></param>
431                 protected void BaseAdd( string name, object value )
432                 {
433                         if (this.IsReadOnly)
434                                 throw new NotSupportedException("Collection is read-only");
435                         
436                         _Item newitem=new _Item(name, value);
437
438                         if (name==null){
439                                 //todo: consider nullkey entry
440                                 if (m_NullKeyItem==null)
441                                         m_NullKeyItem = newitem;
442                         }
443                         else
444                                 if (m_ItemsContainer[name]==null){
445                                         m_ItemsContainer.Add(name,newitem);
446                                 }
447                         m_ItemsArray.Add(newitem);
448                 }
449
450                 protected void BaseClear()
451                 {
452                         if (this.IsReadOnly)
453                                 throw new NotSupportedException("Collection is read-only");
454                         Init();
455                 }
456
457                 /// <summary>
458                 /// SDK: Gets the value of the entry at the specified index of the NameObjectCollectionBase instance.
459                 /// </summary>
460                 /// <param name="index"></param>
461                 /// <returns></returns>
462                 protected object BaseGet( int index )
463                 {
464                         return ((_Item)m_ItemsArray[index]).value;
465                 }
466
467                 /// <summary>
468                 /// SDK: Gets the value of the first entry with the specified key from the NameObjectCollectionBase instance.
469                 /// </summary>
470                 /// <remark>CAUTION: The BaseGet method does not distinguish between a null reference which is returned because the specified key is not found and a null reference which is returned because the value associated with the key is a null reference.</remark>
471                 /// <param name="name"></param>
472                 /// <returns></returns>
473                 protected object BaseGet( string name )
474                 {
475                         _Item item = FindFirstMatchedItem(name);
476                         /// CAUTION: The BaseGet method does not distinguish between a null reference which is returned because the specified key is not found and a null reference which is returned because the value associated with the key is a null reference.
477                         if (item==null)
478                                 return null;
479                         else
480                                 return item.value;
481                 }
482
483                 /// <summary>
484                 /// SDK:Returns a String array that contains all the keys in the NameObjectCollectionBase instance.
485                 /// </summary>
486                 /// <returns>A String array that contains all the keys in the NameObjectCollectionBase instance.</returns>
487                 protected string[] BaseGetAllKeys()
488                 {
489                         int cnt = m_ItemsArray.Count;
490                         string[] allKeys = new string[cnt];
491                         for(int i=0; i<cnt; i++)
492                                 allKeys[i] = BaseGetKey(i);//((_Item)m_ItemsArray[i]).key;
493                         
494                         return allKeys;
495                 }
496
497                 /// <summary>
498                 /// SDK: Returns an Object array that contains all the values in the NameObjectCollectionBase instance.
499                 /// </summary>
500                 /// <returns>An Object array that contains all the values in the NameObjectCollectionBase instance.</returns>
501                 protected object[] BaseGetAllValues()
502                 {
503                         int cnt = m_ItemsArray.Count;
504                         object[] allValues = new object[cnt];
505                         for(int i=0; i<cnt; i++)
506                                 allValues[i] = BaseGet(i);
507                         
508                         return allValues;
509                 }
510
511                 protected object[] BaseGetAllValues( Type type )
512                 {
513                         if (type == null)
514                                 throw new ArgumentNullException("'type' argument can't be null");
515                         int cnt = m_ItemsArray.Count;
516                         object[] allValues = (object[]) Array.CreateInstance (type, cnt);
517                         for(int i=0; i<cnt; i++)
518                                 allValues[i] = BaseGet(i);
519                         
520                         return allValues;
521                 }
522                 
523                 protected string BaseGetKey( int index )
524                 {
525                         return ((_Item)m_ItemsArray[index]).key;
526                 }
527
528                 /// <summary>
529                 /// Gets a value indicating whether the NameObjectCollectionBase instance contains entries whose keys are not a null reference 
530                 /// </summary>
531                 /// <returns>true if the NameObjectCollectionBase instance contains entries whose keys are not a null reference otherwise, false.</returns>
532                 protected bool BaseHasKeys()
533                 {
534                         return (m_ItemsContainer.Count>0);
535                 }
536
537                 protected void BaseRemove( string name )
538                 {
539                         int cnt = 0;
540                         String key;
541                         if (this.IsReadOnly)
542                                 throw new NotSupportedException("Collection is read-only");
543                         if (name!=null)
544                         {
545                                 m_ItemsContainer.Remove(name);
546                         }
547                         else {
548                                 m_NullKeyItem = null;
549                         }
550                         
551                         cnt = m_ItemsArray.Count;
552                         for (int i=0 ; i< cnt; ){
553                                 key=BaseGetKey(i);
554                                 if (Equals (key, name)) {
555                                         m_ItemsArray.RemoveAt(i);
556                                         cnt--;
557                                 }
558                                 else 
559                                         i++;
560                         }
561                 }
562
563                 /// <summary>
564                 /// 
565                 /// </summary>
566                 /// <param name="index"></param>
567                 /// <LAME>This function implemented the way Microsoft implemented it - 
568                 /// item is removed from hashtable and array without considering the case when there are two items with the same key but different values in array.
569                 /// E.g. if
570                 /// hashtable is [("Key1","value1")] and array contains [("Key1","value1")("Key1","value2")] then
571                 /// after RemoveAt(1) the collection will be in following state:
572                 /// hashtable:[] 
573                 /// array: [("Key1","value1")] 
574                 /// It's ok only then the key is uniquely assosiated with the value
575                 /// To fix it a comparsion of objects stored under the same key in the hashtable and in the arraylist should be added 
576                 /// </LAME>>
577                 protected void BaseRemoveAt( int index )
578                 {
579                         if (this.IsReadOnly)
580                                 throw new NotSupportedException("Collection is read-only");
581                         string key = BaseGetKey(index);
582                         if (key!=null){
583                                 // TODO: see LAME description above
584                                 m_ItemsContainer.Remove(key);
585                         }
586                         else
587                                 m_NullKeyItem = null;
588                         m_ItemsArray.RemoveAt(index);
589                 }
590
591                 /// <summary>
592                 /// SDK: Sets the value of the entry at the specified index of the NameObjectCollectionBase instance.
593                 /// </summary>
594                 /// <param name="index"></param>
595                 /// <param name="value"></param>
596                 protected void BaseSet( int index, object value )
597                 {
598                         if (this.IsReadOnly)
599                                 throw new NotSupportedException("Collection is read-only");
600                         _Item item = (_Item)m_ItemsArray[index];
601                         item.value = value;
602                 }
603
604                 /// <summary>
605                 /// Sets the value of the first entry with the specified key in the NameObjectCollectionBase instance, if found; otherwise, adds an entry with the specified key and value into the NameObjectCollectionBase instance.
606                 /// </summary>
607                 /// <param name="name">The String key of the entry to set. The key can be a null reference </param>
608                 /// <param name="value">The Object that represents the new value of the entry to set. The value can be a null reference</param>
609                 protected void BaseSet( string name, object value )
610                 {
611                         if (this.IsReadOnly)
612                                 throw new NotSupportedException("Collection is read-only");
613                         _Item item = FindFirstMatchedItem(name);
614                         if (item!=null)
615                                 item.value=value;
616                         else 
617                                 BaseAdd(name, value);
618                 }
619
620                 [MonoTODO]
621                 private _Item FindFirstMatchedItem(string name)
622                 {
623                         if (name!=null)
624                                 return (_Item)m_ItemsContainer[name];
625                         else {
626                                 //TODO: consider null key case
627                                 return m_NullKeyItem;
628                         }
629                 }
630
631                 internal bool Equals (string s1, string s2)
632                 {
633 #if NET_2_0
634                         if (m_comparer != null)
635                                 return (m_comparer.Compare (s1, s2) == 0);
636                         else
637                                 return equality_comparer.Equals (s1, s2);
638 #else
639                         return (m_comparer.Compare (s1, s2) == 0);
640 #endif
641                 }
642         }
643 }