2009-08-20 Sebastien Pouliot <sebastien@ximian.com>
[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);
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 array, int arrayIndex)
132                         {
133                                 ArrayList items = m_collection.m_ItemsArray;
134 #if NET_2_0
135                                 if (null == array)
136                                         throw new ArgumentNullException ("array");
137
138                                 if (arrayIndex < 0)
139                                         throw new ArgumentOutOfRangeException ("arrayIndex");
140
141                                 if ((array.Length > 0) && (arrayIndex >= array.Length))
142                                         throw new ArgumentException ("arrayIndex is equal to or greater than array.Length");
143
144                                 if (arrayIndex + items.Count > array.Length)
145                                         throw new ArgumentException ("Not enough room from arrayIndex to end of array for this KeysCollection");
146 #endif
147
148                                 if (array != null && array.Rank > 1)
149                                         throw new ArgumentException ("array is multidimensional");
150                                 
151                                 object[] objArray = (object[])array;
152                                 for (int i = 0; i < items.Count; i++, arrayIndex++)
153                                         objArray [arrayIndex] = ((_Item)items [i]).key;
154                         }
155
156                         bool ICollection.IsSynchronized
157                         {
158                                 get{
159                                         return false;
160                                 }
161                         }
162                         object ICollection.SyncRoot
163                         {
164                                 get{
165                                         return m_collection;
166                                 }
167                         }
168                         /// <summary>
169                         /// Gets the number of keys in the NameObjectCollectionBase.KeysCollection
170                         /// </summary>
171                         public int Count 
172                         {
173                                 get{
174                                         return m_collection.Count;
175                                 }
176                         }
177
178                         public string this [int index] {
179                                 get { return Get (index); }
180                         }
181
182                         // IEnumerable methods --------------------------------
183                         /// <summary>
184                         /// SDK: Returns an enumerator that can iterate through the NameObjectCollectionBase.KeysCollection.
185                         /// </summary>
186                         /// <returns></returns>
187                         public IEnumerator GetEnumerator()
188                         {
189                                 return new _KeysEnumerator(m_collection);
190                         }
191                 }
192
193                 //--------------- Protected Instance Constructors --------------
194                 
195                 /// <summary>
196                 /// SDK: Initializes a new instance of the NameObjectCollectionBase class that is empty.
197                 /// </summary>
198                 protected NameObjectCollectionBase ()
199                 {
200                         m_readonly = false;
201 #if NET_1_0
202                         m_hashprovider = CaseInsensitiveHashCodeProvider.Default;
203                         m_comparer = CaseInsensitiveComparer.Default;
204 #else
205                         m_hashprovider = CaseInsensitiveHashCodeProvider.DefaultInvariant;
206                         m_comparer = CaseInsensitiveComparer.DefaultInvariant;
207 #endif
208                         m_defCapacity = 0;
209                         Init();
210                 }
211                 
212                 protected NameObjectCollectionBase( int capacity )
213                 {
214                         m_readonly = false;
215 #if NET_1_0
216                         m_hashprovider = CaseInsensitiveHashCodeProvider.Default;
217                         m_comparer = CaseInsensitiveComparer.Default;
218 #else
219                         m_hashprovider = CaseInsensitiveHashCodeProvider.DefaultInvariant;
220                         m_comparer = CaseInsensitiveComparer.DefaultInvariant;
221 #endif
222                         m_defCapacity = capacity;
223                         Init();
224                 }               
225
226 #if NET_2_0
227
228                 internal NameObjectCollectionBase (IEqualityComparer equalityComparer, IComparer comparer, IHashCodeProvider hcp)
229                 {
230                         equality_comparer = equalityComparer;
231                         m_comparer = comparer;
232                         m_hashprovider = hcp;
233                         m_readonly = false;
234                         m_defCapacity = 0;
235                         Init ();
236                 }
237
238                 protected NameObjectCollectionBase (IEqualityComparer equalityComparer) : this( (equalityComparer == null ? StringComparer.InvariantCultureIgnoreCase : equalityComparer), null, null)
239                 {                       
240                 }               
241
242                 [Obsolete ("Use NameObjectCollectionBase(IEqualityComparer)")]
243 #endif
244                 protected NameObjectCollectionBase( IHashCodeProvider hashProvider, IComparer comparer )
245                 {                       
246                         m_comparer = comparer;
247                         m_hashprovider = hashProvider;
248                         m_readonly = false;
249                         m_defCapacity = 0;
250                         Init ();
251                 }
252
253                 protected NameObjectCollectionBase (SerializationInfo info, StreamingContext context)
254                 {
255                         infoCopy = info;
256                 }
257
258 #if NET_2_0
259                 protected NameObjectCollectionBase (int capacity, IEqualityComparer equalityComparer)
260                 {
261                         m_readonly = false;
262                         equality_comparer = (equalityComparer == null ? StringComparer.InvariantCultureIgnoreCase : equalityComparer);
263                         m_defCapacity = capacity;
264                         Init();
265                 }
266
267                 [Obsolete ("Use NameObjectCollectionBase(int,IEqualityComparer)")]
268 #endif
269                 protected NameObjectCollectionBase( int capacity, IHashCodeProvider hashProvider, IComparer comparer )
270                 {
271                         m_readonly = false;
272                         
273                         m_hashprovider = hashProvider;
274                         m_comparer = comparer;
275                         m_defCapacity = capacity;
276                         Init();
277                 }
278                 
279                 private void Init ()
280                 {
281 #if NET_2_0
282                         if (equality_comparer != null)
283                                 m_ItemsContainer = new Hashtable (m_defCapacity, equality_comparer);
284                         else
285                                 m_ItemsContainer = new Hashtable (m_defCapacity, m_hashprovider, m_comparer);
286 #else
287                         m_ItemsContainer = new Hashtable (m_defCapacity, m_hashprovider, m_comparer);
288 #endif
289                         m_ItemsArray = new ArrayList();
290                         m_NullKeyItem = null;   
291                 }
292
293                 //--------------- Public Instance Properties -------------------
294
295                 public virtual NameObjectCollectionBase.KeysCollection Keys {
296                         get {
297                                 if (keyscoll == null)
298                                         keyscoll = new KeysCollection (this);
299                                 return keyscoll;
300                         }
301                 }
302                                 
303                 //--------------- Public Instance Methods ----------------------
304                 // 
305                 /// <summary>
306                 /// SDK: Returns an enumerator that can iterate through the NameObjectCollectionBase.
307                 /// 
308                 /// <remark>This enumerator returns the keys of the collection as strings.</remark>
309                 /// </summary>
310                 /// <returns></returns>
311                 public
312 #if NET_2_0             
313                 virtual
314 #endif
315                 IEnumerator GetEnumerator()
316                 {
317                         return new _KeysEnumerator(this);
318                 }
319
320                 // ISerializable
321                 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
322                 {
323                         if (info == null)
324                                 throw new ArgumentNullException ("info");
325
326                         int count = Count;
327                         string [] keys = new string [count];
328                         object [] values = new object [count];
329                         int i = 0;
330                         foreach (_Item item in m_ItemsArray) {
331                                 keys [i] = item.key;
332                                 values [i] = item.value;
333                                 i++;
334                         }
335
336 #if NET_2_0
337                         if (equality_comparer != null) {
338                                 info.AddValue ("KeyComparer", equality_comparer, typeof (IEqualityComparer));
339                                 info.AddValue ("Version", 4, typeof (int));
340                         } else {
341                                 info.AddValue ("HashProvider", m_hashprovider, typeof (IHashCodeProvider));
342                                 info.AddValue ("Comparer", m_comparer, typeof (IComparer));
343                                 info.AddValue ("Version", 2, typeof (int));
344                         }
345 #else
346                         info.AddValue ("HashProvider", m_hashprovider, typeof (IHashCodeProvider));
347                         info.AddValue ("Comparer", m_comparer, typeof (IComparer));
348 #endif
349                         info.AddValue("ReadOnly", m_readonly);
350                         info.AddValue("Count", count);
351                         info.AddValue("Keys", keys, typeof(string[]));
352                         info.AddValue("Values", values, typeof(object[]));
353                 }
354
355                 // ICollection
356                 public virtual int Count 
357                 {
358                         get{
359                                 return m_ItemsArray.Count;
360                         }
361                 }
362
363                 bool ICollection.IsSynchronized
364                 {
365                         get { return false; }
366                 }
367
368                 object ICollection.SyncRoot
369                 {
370                         get { return this; }
371                 }
372
373                 void ICollection.CopyTo (Array array, int index)
374                 {
375                         ((ICollection)Keys).CopyTo (array, index);
376                 }
377
378                 // IDeserializationCallback
379                 public virtual void OnDeserialization (object sender)
380                 {
381                         SerializationInfo info = infoCopy;
382                         
383                         // If a subclass overrides the serialization constructor
384                         // and inplements its own serialization process, infoCopy will
385                         // be null and we can ignore this callback.
386                         if (info == null)
387                                 return;
388
389                         infoCopy = null;
390                         m_hashprovider = (IHashCodeProvider) info.GetValue ("HashProvider",
391                                                                             typeof (IHashCodeProvider));
392 #if NET_2_0
393                         if (m_hashprovider == null) {
394                                 equality_comparer = (IEqualityComparer) info.GetValue ("KeyComparer", typeof (IEqualityComparer));
395                         } else {
396                                 m_comparer = (IComparer) info.GetValue ("Comparer", typeof (IComparer));
397                                 if (m_comparer == null)
398                                         throw new SerializationException ("The comparer is null");
399                         }
400 #else
401                         if (m_hashprovider == null)
402                                 throw new SerializationException ("The hash provider is null");
403
404                         m_comparer = (IComparer) info.GetValue ("Comparer", typeof (IComparer));
405                         if (m_comparer == null)
406                                 throw new SerializationException ("The comparer is null");
407 #endif
408                         m_readonly = info.GetBoolean ("ReadOnly");
409                         string [] keys = (string []) info.GetValue ("Keys", typeof (string []));
410                         if (keys == null)
411                                 throw new SerializationException ("keys is null");
412
413                         object [] values = (object []) info.GetValue ("Values", typeof (object []));
414                         if (values == null)
415                                 throw new SerializationException ("values is null");
416
417                         Init ();
418                         int count = keys.Length;
419                         for (int i = 0; i < count; i++)
420                                 BaseAdd (keys [i], values [i]);
421                 }
422
423                 //--------------- Protected Instance Properties ----------------
424                 /// <summary>
425                 /// SDK: Gets or sets a value indicating whether the NameObjectCollectionBase instance is read-only.
426                 /// </summary>
427                 protected bool IsReadOnly 
428                 {
429                         get{
430                                 return m_readonly;
431                         }
432                         set{
433                                 m_readonly=value;
434                         }
435                 }
436                 
437                 //--------------- Protected Instance Methods -------------------
438                 /// <summary>
439                 /// Adds an Item with the specified key and value into the <see cref="NameObjectCollectionBase"/>NameObjectCollectionBase instance.
440                 /// </summary>
441                 /// <param name="name"></param>
442                 /// <param name="value"></param>
443                 protected void BaseAdd( string name, object value )
444                 {
445                         if (this.IsReadOnly)
446                                 throw new NotSupportedException("Collection is read-only");
447                         
448                         _Item newitem=new _Item(name, value);
449
450                         if (name==null){
451                                 //todo: consider nullkey entry
452                                 if (m_NullKeyItem==null)
453                                         m_NullKeyItem = newitem;
454                         }
455                         else
456                                 if (m_ItemsContainer[name]==null){
457                                         m_ItemsContainer.Add(name,newitem);
458                                 }
459                         m_ItemsArray.Add(newitem);
460                 }
461
462                 protected void BaseClear()
463                 {
464                         if (this.IsReadOnly)
465                                 throw new NotSupportedException("Collection is read-only");
466                         Init();
467                 }
468
469                 /// <summary>
470                 /// SDK: Gets the value of the entry at the specified index of the NameObjectCollectionBase instance.
471                 /// </summary>
472                 /// <param name="index"></param>
473                 /// <returns></returns>
474                 protected object BaseGet( int index )
475                 {
476                         return ((_Item)m_ItemsArray[index]).value;
477                 }
478
479                 /// <summary>
480                 /// SDK: Gets the value of the first entry with the specified key from the NameObjectCollectionBase instance.
481                 /// </summary>
482                 /// <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>
483                 /// <param name="name"></param>
484                 /// <returns></returns>
485                 protected object BaseGet( string name )
486                 {
487                         _Item item = FindFirstMatchedItem(name);
488                         /// 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.
489                         if (item==null)
490                                 return null;
491                         else
492                                 return item.value;
493                 }
494
495                 /// <summary>
496                 /// SDK:Returns a String array that contains all the keys in the NameObjectCollectionBase instance.
497                 /// </summary>
498                 /// <returns>A String array that contains all the keys in the NameObjectCollectionBase instance.</returns>
499                 protected string[] BaseGetAllKeys()
500                 {
501                         int cnt = m_ItemsArray.Count;
502                         string[] allKeys = new string[cnt];
503                         for(int i=0; i<cnt; i++)
504                                 allKeys[i] = BaseGetKey(i);//((_Item)m_ItemsArray[i]).key;
505                         
506                         return allKeys;
507                 }
508
509                 /// <summary>
510                 /// SDK: Returns an Object array that contains all the values in the NameObjectCollectionBase instance.
511                 /// </summary>
512                 /// <returns>An Object array that contains all the values in the NameObjectCollectionBase instance.</returns>
513                 protected object[] BaseGetAllValues()
514                 {
515                         int cnt = m_ItemsArray.Count;
516                         object[] allValues = new object[cnt];
517                         for(int i=0; i<cnt; i++)
518                                 allValues[i] = BaseGet(i);
519                         
520                         return allValues;
521                 }
522
523                 protected object[] BaseGetAllValues( Type type )
524                 {
525                         if (type == null)
526                                 throw new ArgumentNullException("'type' argument can't be null");
527                         int cnt = m_ItemsArray.Count;
528                         object[] allValues = (object[]) Array.CreateInstance (type, cnt);
529                         for(int i=0; i<cnt; i++)
530                                 allValues[i] = BaseGet(i);
531                         
532                         return allValues;
533                 }
534                 
535                 protected string BaseGetKey( int index )
536                 {
537                         return ((_Item)m_ItemsArray[index]).key;
538                 }
539
540                 /// <summary>
541                 /// Gets a value indicating whether the NameObjectCollectionBase instance contains entries whose keys are not a null reference 
542                 /// </summary>
543                 /// <returns>true if the NameObjectCollectionBase instance contains entries whose keys are not a null reference otherwise, false.</returns>
544                 protected bool BaseHasKeys()
545                 {
546                         return (m_ItemsContainer.Count>0);
547                 }
548
549                 protected void BaseRemove( string name )
550                 {
551                         int cnt = 0;
552                         String key;
553                         if (this.IsReadOnly)
554                                 throw new NotSupportedException("Collection is read-only");
555                         if (name!=null)
556                         {
557                                 m_ItemsContainer.Remove(name);
558                         }
559                         else {
560                                 m_NullKeyItem = null;
561                         }
562                         
563                         cnt = m_ItemsArray.Count;
564                         for (int i=0 ; i< cnt; ){
565                                 key=BaseGetKey(i);
566                                 if (Equals (key, name)) {
567                                         m_ItemsArray.RemoveAt(i);
568                                         cnt--;
569                                 }
570                                 else 
571                                         i++;
572                         }
573                 }
574
575                 /// <summary>
576                 /// 
577                 /// </summary>
578                 /// <param name="index"></param>
579                 /// <LAME>This function implemented the way Microsoft implemented it - 
580                 /// 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.
581                 /// E.g. if
582                 /// hashtable is [("Key1","value1")] and array contains [("Key1","value1")("Key1","value2")] then
583                 /// after RemoveAt(1) the collection will be in following state:
584                 /// hashtable:[] 
585                 /// array: [("Key1","value1")] 
586                 /// It's ok only then the key is uniquely assosiated with the value
587                 /// To fix it a comparsion of objects stored under the same key in the hashtable and in the arraylist should be added 
588                 /// </LAME>>
589                 protected void BaseRemoveAt( int index )
590                 {
591                         if (this.IsReadOnly)
592                                 throw new NotSupportedException("Collection is read-only");
593                         string key = BaseGetKey(index);
594                         if (key!=null){
595                                 // TODO: see LAME description above
596                                 m_ItemsContainer.Remove(key);
597                         }
598                         else
599                                 m_NullKeyItem = null;
600                         m_ItemsArray.RemoveAt(index);
601                 }
602
603                 /// <summary>
604                 /// SDK: Sets the value of the entry at the specified index of the NameObjectCollectionBase instance.
605                 /// </summary>
606                 /// <param name="index"></param>
607                 /// <param name="value"></param>
608                 protected void BaseSet( int index, object value )
609                 {
610 #if NET_2_0
611                         if (this.IsReadOnly)
612                                 throw new NotSupportedException("Collection is read-only");
613 #endif
614                         _Item item = (_Item)m_ItemsArray[index];
615                         item.value = value;
616                 }
617
618                 /// <summary>
619                 /// 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.
620                 /// </summary>
621                 /// <param name="name">The String key of the entry to set. The key can be a null reference </param>
622                 /// <param name="value">The Object that represents the new value of the entry to set. The value can be a null reference</param>
623                 protected void BaseSet( string name, object value )
624                 {
625 #if NET_2_0
626                         if (this.IsReadOnly)
627                                 throw new NotSupportedException("Collection is read-only");
628 #endif
629                         _Item item = FindFirstMatchedItem(name);
630                         if (item!=null)
631                                 item.value=value;
632                         else 
633                                 BaseAdd(name, value);
634                 }
635
636                 [MonoTODO]
637                 private _Item FindFirstMatchedItem(string name)
638                 {
639                         if (name!=null)
640                                 return (_Item)m_ItemsContainer[name];
641                         else {
642                                 //TODO: consider null key case
643                                 return m_NullKeyItem;
644                         }
645                 }
646
647                 internal bool Equals (string s1, string s2)
648                 {
649 #if NET_2_0
650                         if (m_comparer != null)
651                                 return (m_comparer.Compare (s1, s2) == 0);
652                         else
653                                 return equality_comparer.Equals (s1, s2);
654 #else
655                         return (m_comparer.Compare (s1, s2) == 0);
656 #endif
657                 }
658         }
659 }