Merge branch 'master' of github.com:mono/mono
[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                 private IEqualityComparer equality_comparer;
53
54                 internal IEqualityComparer EqualityComparer {
55                         get { return equality_comparer; }
56                 }
57
58                 internal IComparer Comparer {
59                         get {return m_comparer;}
60                 }
61
62                 internal IHashCodeProvider HashCodeProvider {
63                         get {return m_hashprovider;}
64                 }
65
66                 internal class _Item
67                 {
68                         public string key;
69                         public object value;
70                         public _Item(string key, object value)
71                         {
72                                 this.key = key;
73                                 this.value = value;
74                         }
75                 }               
76                 /// <summary>
77                 /// Implements IEnumerable interface for KeysCollection
78                 /// </summary>
79                 [Serializable]
80                 internal class _KeysEnumerator : IEnumerator
81                 {
82                         private NameObjectCollectionBase m_collection;
83                         private int m_position;
84
85                         internal _KeysEnumerator(NameObjectCollectionBase collection)
86                         {
87                                 m_collection = collection;
88                                 Reset();
89                         }
90                         public object Current 
91                         {
92                                 
93                                 get{
94                                         if ((m_position<m_collection.Count)||(m_position<0))
95                                                 return m_collection.BaseGetKey(m_position);
96                                         else 
97                                                 throw new InvalidOperationException();
98                                 }
99                                 
100                         }
101                         public bool MoveNext()
102                         {
103                                 return ((++m_position) < m_collection.Count);
104                         }
105                         public void Reset()
106                         {
107                                 m_position = -1;
108                         }
109                 }
110                 
111                 /// <summary>
112                 /// SDK: Represents a collection of the String keys of a collection.
113                 /// </summary>
114                 [Serializable]
115                 public class KeysCollection : ICollection, IEnumerable
116                 {
117                         private NameObjectCollectionBase m_collection;
118
119                         internal KeysCollection (NameObjectCollectionBase collection)
120                         {
121                                 this.m_collection = collection;
122                         }
123
124                         public virtual string Get( int index )
125                         {
126                                 return m_collection.BaseGetKey(index);
127                         }
128                         
129                         // ICollection methods -----------------------------------
130                         void ICollection.CopyTo (Array array, int arrayIndex)
131                         {
132                                 ArrayList items = m_collection.m_ItemsArray;
133                                 if (null == array)
134                                         throw new ArgumentNullException ("array");
135
136                                 if (arrayIndex < 0)
137                                         throw new ArgumentOutOfRangeException ("arrayIndex");
138
139                                 if ((array.Length > 0) && (arrayIndex >= array.Length))
140                                         throw new ArgumentException ("arrayIndex is equal to or greater than array.Length");
141
142                                 if (arrayIndex + items.Count > array.Length)
143                                         throw new ArgumentException ("Not enough room from arrayIndex to end of array for this KeysCollection");
144
145                                 if (array != null && array.Rank > 1)
146                                         throw new ArgumentException ("array is multidimensional");
147                                 
148                                 object[] objArray = (object[])array;
149                                 for (int i = 0; i < items.Count; i++, arrayIndex++)
150                                         objArray [arrayIndex] = ((_Item)items [i]).key;
151                         }
152
153                         bool ICollection.IsSynchronized
154                         {
155                                 get{
156                                         return false;
157                                 }
158                         }
159                         object ICollection.SyncRoot
160                         {
161                                 get{
162                                         return m_collection;
163                                 }
164                         }
165                         /// <summary>
166                         /// Gets the number of keys in the NameObjectCollectionBase.KeysCollection
167                         /// </summary>
168                         public int Count 
169                         {
170                                 get{
171                                         return m_collection.Count;
172                                 }
173                         }
174
175                         public string this [int index] {
176                                 get { return Get (index); }
177                         }
178
179                         // IEnumerable methods --------------------------------
180                         /// <summary>
181                         /// SDK: Returns an enumerator that can iterate through the NameObjectCollectionBase.KeysCollection.
182                         /// </summary>
183                         /// <returns></returns>
184                         public IEnumerator GetEnumerator()
185                         {
186                                 return new _KeysEnumerator(m_collection);
187                         }
188                 }
189
190                 //--------------- Protected Instance Constructors --------------
191                 
192                 /// <summary>
193                 /// SDK: Initializes a new instance of the NameObjectCollectionBase class that is empty.
194                 /// </summary>
195                 protected NameObjectCollectionBase ()
196                 {
197                         m_readonly = false;
198 #if NET_1_0
199                         m_hashprovider = CaseInsensitiveHashCodeProvider.Default;
200                         m_comparer = CaseInsensitiveComparer.Default;
201 #else
202                         m_hashprovider = CaseInsensitiveHashCodeProvider.DefaultInvariant;
203                         m_comparer = CaseInsensitiveComparer.DefaultInvariant;
204 #endif
205                         m_defCapacity = 0;
206                         Init();
207                 }
208                 
209                 protected NameObjectCollectionBase( int capacity )
210                 {
211                         m_readonly = false;
212 #if NET_1_0
213                         m_hashprovider = CaseInsensitiveHashCodeProvider.Default;
214                         m_comparer = CaseInsensitiveComparer.Default;
215 #else
216                         m_hashprovider = CaseInsensitiveHashCodeProvider.DefaultInvariant;
217                         m_comparer = CaseInsensitiveComparer.DefaultInvariant;
218 #endif
219                         m_defCapacity = capacity;
220                         Init();
221                 }               
222
223                 internal NameObjectCollectionBase (IEqualityComparer equalityComparer, IComparer comparer, IHashCodeProvider hcp)
224                 {
225                         equality_comparer = equalityComparer;
226                         m_comparer = comparer;
227                         m_hashprovider = hcp;
228                         m_readonly = false;
229                         m_defCapacity = 0;
230                         Init ();
231                 }
232
233                 protected NameObjectCollectionBase (IEqualityComparer equalityComparer) : this( (equalityComparer == null ? StringComparer.InvariantCultureIgnoreCase : equalityComparer), null, null)
234                 {                       
235                 }               
236
237                 [Obsolete ("Use NameObjectCollectionBase(IEqualityComparer)")]
238                 protected NameObjectCollectionBase( IHashCodeProvider hashProvider, IComparer comparer )
239                 {                       
240                         m_comparer = comparer;
241                         m_hashprovider = hashProvider;
242                         m_readonly = false;
243                         m_defCapacity = 0;
244                         Init ();
245                 }
246
247                 protected NameObjectCollectionBase (SerializationInfo info, StreamingContext context)
248                 {
249                         infoCopy = info;
250                 }
251
252                 protected NameObjectCollectionBase (int capacity, IEqualityComparer equalityComparer)
253                 {
254                         m_readonly = false;
255                         equality_comparer = (equalityComparer == null ? StringComparer.InvariantCultureIgnoreCase : equalityComparer);
256                         m_defCapacity = capacity;
257                         Init();
258                 }
259
260                 [Obsolete ("Use NameObjectCollectionBase(int,IEqualityComparer)")]
261                 protected NameObjectCollectionBase( int capacity, IHashCodeProvider hashProvider, IComparer comparer )
262                 {
263                         m_readonly = false;
264                         
265                         m_hashprovider = hashProvider;
266                         m_comparer = comparer;
267                         m_defCapacity = capacity;
268                         Init();
269                 }
270                 
271                 private void Init ()
272                 {
273                         if (m_ItemsContainer != null) {
274                                 m_ItemsContainer.Clear ();
275                                 m_ItemsContainer = null;
276                         }
277                         
278                         if (m_ItemsArray != null) {
279                                 m_ItemsArray.Clear ();
280                                 m_ItemsArray = null;
281                         }
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                         m_ItemsArray = new ArrayList();
287                         m_NullKeyItem = null;   
288                 }
289
290                 //--------------- Public Instance Properties -------------------
291
292                 public virtual NameObjectCollectionBase.KeysCollection Keys {
293                         get {
294                                 if (keyscoll == null)
295                                         keyscoll = new KeysCollection (this);
296                                 return keyscoll;
297                         }
298                 }
299                                 
300                 //--------------- Public Instance Methods ----------------------
301                 // 
302                 /// <summary>
303                 /// SDK: Returns an enumerator that can iterate through the NameObjectCollectionBase.
304                 /// 
305                 /// <remark>This enumerator returns the keys of the collection as strings.</remark>
306                 /// </summary>
307                 /// <returns></returns>
308                 public virtual IEnumerator GetEnumerator()
309                 {
310                         return new _KeysEnumerator(this);
311                 }
312
313                 // ISerializable
314                 public virtual void GetObjectData (SerializationInfo info, StreamingContext context)
315                 {
316                         if (info == null)
317                                 throw new ArgumentNullException ("info");
318
319                         int count = Count;
320                         string [] keys = new string [count];
321                         object [] values = new object [count];
322                         int i = 0;
323                         foreach (_Item item in m_ItemsArray) {
324                                 keys [i] = item.key;
325                                 values [i] = item.value;
326                                 i++;
327                         }
328
329                         if (equality_comparer != null) {
330                                 info.AddValue ("KeyComparer", equality_comparer, typeof (IEqualityComparer));
331                                 info.AddValue ("Version", 4, typeof (int));
332                         } else {
333                                 info.AddValue ("HashProvider", m_hashprovider, typeof (IHashCodeProvider));
334                                 info.AddValue ("Comparer", m_comparer, typeof (IComparer));
335                                 info.AddValue ("Version", 2, typeof (int));
336                         }
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                         ((ICollection)Keys).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 (m_hashprovider == null) {
381                                 equality_comparer = (IEqualityComparer) info.GetValue ("KeyComparer", typeof (IEqualityComparer));
382                         } else {
383                                 m_comparer = (IComparer) info.GetValue ("Comparer", typeof (IComparer));
384                                 if (m_comparer == null)
385                                         throw new SerializationException ("The comparer is null");
386                         }
387                         m_readonly = info.GetBoolean ("ReadOnly");
388                         string [] keys = (string []) info.GetValue ("Keys", typeof (string []));
389                         if (keys == null)
390                                 throw new SerializationException ("keys is null");
391
392                         object [] values = (object []) info.GetValue ("Values", typeof (object []));
393                         if (values == null)
394                                 throw new SerializationException ("values is null");
395
396                         Init ();
397                         int count = keys.Length;
398                         for (int i = 0; i < count; i++)
399                                 BaseAdd (keys [i], values [i]);
400                 }
401
402                 //--------------- Protected Instance Properties ----------------
403                 /// <summary>
404                 /// SDK: Gets or sets a value indicating whether the NameObjectCollectionBase instance is read-only.
405                 /// </summary>
406                 protected bool IsReadOnly 
407                 {
408                         get{
409                                 return m_readonly;
410                         }
411                         set{
412                                 m_readonly=value;
413                         }
414                 }
415                 
416                 //--------------- Protected Instance Methods -------------------
417                 /// <summary>
418                 /// Adds an Item with the specified key and value into the <see cref="NameObjectCollectionBase"/>NameObjectCollectionBase instance.
419                 /// </summary>
420                 /// <param name="name"></param>
421                 /// <param name="value"></param>
422                 protected void BaseAdd( string name, object value )
423                 {
424                         if (this.IsReadOnly)
425                                 throw new NotSupportedException("Collection is read-only");
426                         
427                         _Item newitem=new _Item(name, value);
428
429                         if (name==null){
430                                 //todo: consider nullkey entry
431                                 if (m_NullKeyItem==null)
432                                         m_NullKeyItem = newitem;
433                         }
434                         else
435                                 if (m_ItemsContainer[name]==null){
436                                         m_ItemsContainer.Add(name,newitem);
437                                 }
438                         m_ItemsArray.Add(newitem);
439                 }
440
441                 protected void BaseClear()
442                 {
443                         if (this.IsReadOnly)
444                                 throw new NotSupportedException("Collection is read-only");
445                         Init();
446                 }
447
448                 /// <summary>
449                 /// SDK: Gets the value of the entry at the specified index of the NameObjectCollectionBase instance.
450                 /// </summary>
451                 /// <param name="index"></param>
452                 /// <returns></returns>
453                 protected object BaseGet( int index )
454                 {
455                         return ((_Item)m_ItemsArray[index]).value;
456                 }
457
458                 /// <summary>
459                 /// SDK: Gets the value of the first entry with the specified key from the NameObjectCollectionBase instance.
460                 /// </summary>
461                 /// <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>
462                 /// <param name="name"></param>
463                 /// <returns></returns>
464                 protected object BaseGet( string name )
465                 {
466                         _Item item = FindFirstMatchedItem(name);
467                         /// 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.
468                         if (item==null)
469                                 return null;
470                         else
471                                 return item.value;
472                 }
473
474                 /// <summary>
475                 /// SDK:Returns a String array that contains all the keys in the NameObjectCollectionBase instance.
476                 /// </summary>
477                 /// <returns>A String array that contains all the keys in the NameObjectCollectionBase instance.</returns>
478                 protected string[] BaseGetAllKeys()
479                 {
480                         int cnt = m_ItemsArray.Count;
481                         string[] allKeys = new string[cnt];
482                         for(int i=0; i<cnt; i++)
483                                 allKeys[i] = BaseGetKey(i);//((_Item)m_ItemsArray[i]).key;
484                         
485                         return allKeys;
486                 }
487
488                 /// <summary>
489                 /// SDK: Returns an Object array that contains all the values in the NameObjectCollectionBase instance.
490                 /// </summary>
491                 /// <returns>An Object array that contains all the values in the NameObjectCollectionBase instance.</returns>
492                 protected object[] BaseGetAllValues()
493                 {
494                         int cnt = m_ItemsArray.Count;
495                         object[] allValues = new object[cnt];
496                         for(int i=0; i<cnt; i++)
497                                 allValues[i] = BaseGet(i);
498                         
499                         return allValues;
500                 }
501
502                 protected object[] BaseGetAllValues( Type type )
503                 {
504                         if (type == null)
505                                 throw new ArgumentNullException("'type' argument can't be null");
506                         int cnt = m_ItemsArray.Count;
507                         object[] allValues = (object[]) Array.CreateInstance (type, cnt);
508                         for(int i=0; i<cnt; i++)
509                                 allValues[i] = BaseGet(i);
510                         
511                         return allValues;
512                 }
513                 
514                 protected string BaseGetKey( int index )
515                 {
516                         return ((_Item)m_ItemsArray[index]).key;
517                 }
518
519                 /// <summary>
520                 /// Gets a value indicating whether the NameObjectCollectionBase instance contains entries whose keys are not a null reference 
521                 /// </summary>
522                 /// <returns>true if the NameObjectCollectionBase instance contains entries whose keys are not a null reference otherwise, false.</returns>
523                 protected bool BaseHasKeys()
524                 {
525                         return (m_ItemsContainer.Count>0);
526                 }
527
528                 protected void BaseRemove( string name )
529                 {
530                         int cnt = 0;
531                         String key;
532                         if (this.IsReadOnly)
533                                 throw new NotSupportedException("Collection is read-only");
534                         if (name!=null)
535                         {
536                                 m_ItemsContainer.Remove(name);
537                         }
538                         else {
539                                 m_NullKeyItem = null;
540                         }
541                         
542                         cnt = m_ItemsArray.Count;
543                         for (int i=0 ; i< cnt; ){
544                                 key=BaseGetKey(i);
545                                 if (Equals (key, name)) {
546                                         m_ItemsArray.RemoveAt(i);
547                                         cnt--;
548                                 }
549                                 else 
550                                         i++;
551                         }
552                 }
553
554                 /// <summary>
555                 /// 
556                 /// </summary>
557                 /// <param name="index"></param>
558                 /// <LAME>This function implemented the way Microsoft implemented it - 
559                 /// 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.
560                 /// E.g. if
561                 /// hashtable is [("Key1","value1")] and array contains [("Key1","value1")("Key1","value2")] then
562                 /// after RemoveAt(1) the collection will be in following state:
563                 /// hashtable:[] 
564                 /// array: [("Key1","value1")] 
565                 /// It's ok only then the key is uniquely assosiated with the value
566                 /// To fix it a comparsion of objects stored under the same key in the hashtable and in the arraylist should be added 
567                 /// </LAME>>
568                 protected void BaseRemoveAt( int index )
569                 {
570                         if (this.IsReadOnly)
571                                 throw new NotSupportedException("Collection is read-only");
572                         string key = BaseGetKey(index);
573                         if (key!=null){
574                                 // TODO: see LAME description above
575                                 m_ItemsContainer.Remove(key);
576                         }
577                         else
578                                 m_NullKeyItem = null;
579                         m_ItemsArray.RemoveAt(index);
580                 }
581
582                 /// <summary>
583                 /// SDK: Sets the value of the entry at the specified index of the NameObjectCollectionBase instance.
584                 /// </summary>
585                 /// <param name="index"></param>
586                 /// <param name="value"></param>
587                 protected void BaseSet( int index, object value )
588                 {
589                         if (this.IsReadOnly)
590                                 throw new NotSupportedException("Collection is read-only");
591                         _Item item = (_Item)m_ItemsArray[index];
592                         item.value = value;
593                 }
594
595                 /// <summary>
596                 /// 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.
597                 /// </summary>
598                 /// <param name="name">The String key of the entry to set. The key can be a null reference </param>
599                 /// <param name="value">The Object that represents the new value of the entry to set. The value can be a null reference</param>
600                 protected void BaseSet( string name, object value )
601                 {
602                         if (this.IsReadOnly)
603                                 throw new NotSupportedException("Collection is read-only");
604                         _Item item = FindFirstMatchedItem(name);
605                         if (item!=null)
606                                 item.value=value;
607                         else 
608                                 BaseAdd(name, value);
609                 }
610
611                 [MonoTODO]
612                 private _Item FindFirstMatchedItem(string name)
613                 {
614                         if (name!=null)
615                                 return (_Item)m_ItemsContainer[name];
616                         else {
617                                 //TODO: consider null key case
618                                 return m_NullKeyItem;
619                         }
620                 }
621
622                 internal bool Equals (string s1, string s2)
623                 {
624                         if (m_comparer != null)
625                                 return (m_comparer.Compare (s1, s2) == 0);
626                         else
627                                 return equality_comparer.Equals (s1, s2);
628                 }
629         }
630 }