1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 /*
6  * Ordered String/Object collection of name/value pairs with support for null key
7  *
8  * This class is intended to be used as a base class
9  *
10  */
11 
12 #pragma warning disable 618 // obsolete types, namely IHashCodeProvider
13 
14 using System.Globalization;
15 using System.Runtime.Serialization;
16 
17 namespace System.Collections.Specialized
18 {
19     /// <devdoc>
20     /// <para>Provides the <see langword='abstract '/>base class for a sorted collection of associated <see cref='System.String' qualify='true'/> keys
21     ///    and <see cref='System.Object' qualify='true'/> values that can be accessed either with the hash code of
22     ///    the key or with the index.</para>
23     /// </devdoc>
24     [Serializable]
25     public abstract class NameObjectCollectionBase : ICollection, ISerializable, IDeserializationCallback
26     {
27         private bool _readOnly = false;
28         private ArrayList _entriesArray;
29         private IEqualityComparer _keyComparer;
30         private volatile Hashtable _entriesTable;
31         private volatile NameObjectEntry _nullKeyEntry;
32         private KeysCollection _keys;
33         private int _version;
34         private Object _syncRoot;
35 
36         private static readonly StringComparer s_defaultComparer = CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreCase);
37 
38         /// <devdoc>
39         /// <para> Creates an empty <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance with the default initial capacity and using the default case-insensitive hash
40         ///    code provider and the default case-insensitive comparer.</para>
41         /// </devdoc>
NameObjectCollectionBase()42         protected NameObjectCollectionBase() : this(s_defaultComparer)
43         {
44         }
45 
NameObjectCollectionBase(IEqualityComparer equalityComparer)46         protected NameObjectCollectionBase(IEqualityComparer equalityComparer)
47         {
48             _keyComparer = (equalityComparer == null) ? s_defaultComparer : equalityComparer;
49             Reset();
50         }
51 
NameObjectCollectionBase(Int32 capacity, IEqualityComparer equalityComparer)52         protected NameObjectCollectionBase(Int32 capacity, IEqualityComparer equalityComparer) : this(equalityComparer)
53         {
54             Reset(capacity);
55         }
56 
57         [Obsolete("Please use NameObjectCollectionBase(IEqualityComparer) instead.")]
NameObjectCollectionBase(IHashCodeProvider hashProvider, IComparer comparer)58         protected NameObjectCollectionBase(IHashCodeProvider hashProvider, IComparer comparer) {
59             _keyComparer = new CompatibleComparer(hashProvider, comparer);
60             Reset();
61         }
62 
63         [Obsolete("Please use NameObjectCollectionBase(Int32, IEqualityComparer) instead.")]
NameObjectCollectionBase(int capacity, IHashCodeProvider hashProvider, IComparer comparer)64         protected NameObjectCollectionBase(int capacity, IHashCodeProvider hashProvider, IComparer comparer) {
65             _keyComparer = new CompatibleComparer(hashProvider, comparer);
66             Reset(capacity);
67         }
68 
69         /// <devdoc>
70         /// <para>Creates an empty <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance with the specified
71         ///    initial capacity and using the default case-insensitive hash code provider
72         ///    and the default case-insensitive comparer.</para>
73         /// </devdoc>
NameObjectCollectionBase(int capacity)74         protected NameObjectCollectionBase(int capacity)
75         {
76             _keyComparer = s_defaultComparer;
77             Reset(capacity);
78         }
79 
NameObjectCollectionBase(SerializationInfo info, StreamingContext context)80         protected NameObjectCollectionBase(SerializationInfo info, StreamingContext context)
81         {
82             throw new PlatformNotSupportedException();
83         }
84 
GetObjectData(SerializationInfo info, StreamingContext context)85         public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
86         {
87             throw new PlatformNotSupportedException();
88         }
89 
OnDeserialization(object sender)90         public virtual void OnDeserialization(object sender)
91         {
92             throw new PlatformNotSupportedException();
93         }
94 
95         //
96         // Private helpers
97         //
98 
Reset()99         private void Reset()
100         {
101             _entriesArray = new ArrayList();
102             _entriesTable = new Hashtable(_keyComparer);
103             _nullKeyEntry = null;
104             _version++;
105         }
106 
Reset(int capacity)107         private void Reset(int capacity)
108         {
109             _entriesArray = new ArrayList(capacity);
110             _entriesTable = new Hashtable(capacity, _keyComparer);
111             _nullKeyEntry = null;
112             _version++;
113         }
114 
FindEntry(String key)115         private NameObjectEntry FindEntry(String key)
116         {
117             if (key != null)
118                 return (NameObjectEntry)_entriesTable[key];
119             else
120                 return _nullKeyEntry;
121         }
122 
123         internal IEqualityComparer Comparer
124         {
125             get
126             {
127                 return _keyComparer;
128             }
129             set
130             {
131                 _keyComparer = value;
132             }
133         }
134 
135 
136         /// <devdoc>
137         /// <para>Gets or sets a value indicating whether the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance is read-only.</para>
138         /// </devdoc>
139         protected bool IsReadOnly
140         {
141             get { return _readOnly; }
142             set { _readOnly = value; }
143         }
144 
145         /// <devdoc>
146         /// <para>Gets a value indicating whether the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance contains entries whose
147         ///    keys are not <see langword='null'/>.</para>
148         /// </devdoc>
BaseHasKeys()149         protected bool BaseHasKeys()
150         {
151             return (_entriesTable.Count > 0);  // any entries with keys?
152         }
153 
154         //
155         // Methods to add / remove entries
156         //
157 
158         /// <devdoc>
159         ///    <para>Adds an entry with the specified key and value into the
160         ///    <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
161         /// </devdoc>
BaseAdd(String name, Object value)162         protected void BaseAdd(String name, Object value)
163         {
164             if (_readOnly)
165                 throw new NotSupportedException(SR.CollectionReadOnly);
166 
167             NameObjectEntry entry = new NameObjectEntry(name, value);
168 
169             // insert entry into hashtable
170             if (name != null)
171             {
172                 if (_entriesTable[name] == null)
173                     _entriesTable.Add(name, entry);
174             }
175             else
176             { // null key -- special case -- hashtable doesn't like null keys
177                 if (_nullKeyEntry == null)
178                     _nullKeyEntry = entry;
179             }
180 
181             // add entry to the list
182             _entriesArray.Add(entry);
183 
184             _version++;
185         }
186 
187         /// <devdoc>
188         ///    <para>Removes the entries with the specified key from the
189         ///    <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
190         /// </devdoc>
BaseRemove(String name)191         protected void BaseRemove(String name)
192         {
193             if (_readOnly)
194                 throw new NotSupportedException(SR.CollectionReadOnly);
195 
196             if (name != null)
197             {
198                 // remove from hashtable
199                 _entriesTable.Remove(name);
200 
201                 // remove from array
202                 for (int i = _entriesArray.Count - 1; i >= 0; i--)
203                 {
204                     if (_keyComparer.Equals(name, BaseGetKey(i)))
205                         _entriesArray.RemoveAt(i);
206                 }
207             }
208             else
209             { // null key -- special case
210                 // null out special 'null key' entry
211                 _nullKeyEntry = null;
212 
213                 // remove from array
214                 for (int i = _entriesArray.Count - 1; i >= 0; i--)
215                 {
216                     if (BaseGetKey(i) == null)
217                         _entriesArray.RemoveAt(i);
218                 }
219             }
220 
221             _version++;
222         }
223 
224         /// <devdoc>
225         ///    <para> Removes the entry at the specified index of the
226         ///    <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
227         /// </devdoc>
BaseRemoveAt(int index)228         protected void BaseRemoveAt(int index)
229         {
230             if (_readOnly)
231                 throw new NotSupportedException(SR.CollectionReadOnly);
232 
233             String key = BaseGetKey(index);
234 
235             if (key != null)
236             {
237                 // remove from hashtable
238                 _entriesTable.Remove(key);
239             }
240             else
241             { // null key -- special case
242                 // null out special 'null key' entry
243                 _nullKeyEntry = null;
244             }
245 
246             // remove from array
247             _entriesArray.RemoveAt(index);
248 
249             _version++;
250         }
251 
252         /// <devdoc>
253         /// <para>Removes all entries from the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
254         /// </devdoc>
BaseClear()255         protected void BaseClear()
256         {
257             if (_readOnly)
258                 throw new NotSupportedException(SR.CollectionReadOnly);
259 
260             Reset();
261         }
262 
263         //
264         // Access by name
265         //
266 
267         /// <devdoc>
268         ///    <para>Gets the value of the first entry with the specified key from
269         ///       the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
270         /// </devdoc>
BaseGet(String name)271         protected Object BaseGet(String name)
272         {
273             NameObjectEntry e = FindEntry(name);
274             return (e != null) ? e.Value : null;
275         }
276 
277         /// <devdoc>
278         /// <para>Sets the value of the first entry with the specified key in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/>
279         /// instance, if found; otherwise, adds an entry with the specified key and value
280         /// into the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/>
281         /// instance.</para>
282         /// </devdoc>
BaseSet(String name, Object value)283         protected void BaseSet(String name, Object value)
284         {
285             if (_readOnly)
286                 throw new NotSupportedException(SR.CollectionReadOnly);
287 
288             NameObjectEntry entry = FindEntry(name);
289             if (entry != null)
290             {
291                 entry.Value = value;
292                 _version++;
293             }
294             else
295             {
296                 BaseAdd(name, value);
297             }
298         }
299 
300         //
301         // Access by index
302         //
303 
304         /// <devdoc>
305         ///    <para>Gets the value of the entry at the specified index of
306         ///       the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
307         /// </devdoc>
BaseGet(int index)308         protected Object BaseGet(int index)
309         {
310             NameObjectEntry entry = (NameObjectEntry)_entriesArray[index];
311             return entry.Value;
312         }
313 
314         /// <devdoc>
315         ///    <para>Gets the key of the entry at the specified index of the
316         ///    <see cref='System.Collections.Specialized.NameObjectCollectionBase'/>
317         ///    instance.</para>
318         /// </devdoc>
BaseGetKey(int index)319         protected String BaseGetKey(int index)
320         {
321             NameObjectEntry entry = (NameObjectEntry)_entriesArray[index];
322             return entry.Key;
323         }
324 
325         /// <devdoc>
326         ///    <para>Sets the value of the entry at the specified index of
327         ///       the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
328         /// </devdoc>
BaseSet(int index, Object value)329         protected void BaseSet(int index, Object value)
330         {
331             if (_readOnly)
332                 throw new NotSupportedException(SR.CollectionReadOnly);
333 
334             NameObjectEntry entry = (NameObjectEntry)_entriesArray[index];
335             entry.Value = value;
336             _version++;
337         }
338 
339         //
340         // ICollection implementation
341         //
342 
343         /// <devdoc>
344         /// <para>Returns an enumerator that can iterate through the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/>.</para>
345         /// </devdoc>
GetEnumerator()346         public virtual IEnumerator GetEnumerator()
347         {
348             return new NameObjectKeysEnumerator(this);
349         }
350 
351         /// <devdoc>
352         /// <para>Gets the number of key-and-value pairs in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
353         /// </devdoc>
354         public virtual int Count
355         {
356             get
357             {
358                 return _entriesArray.Count;
359             }
360         }
361 
ICollection.CopyTo(Array array, int index)362         void ICollection.CopyTo(Array array, int index)
363         {
364             if (array == null)
365             {
366                 throw new ArgumentNullException(nameof(array));
367             }
368 
369             if (array.Rank != 1)
370             {
371                 throw new ArgumentException(SR.Arg_MultiRank, nameof(array));
372             }
373 
374             if (index < 0)
375             {
376                 throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
377             }
378 
379             if (array.Length - index < _entriesArray.Count)
380             {
381                 throw new ArgumentException(SR.Arg_InsufficientSpace);
382             }
383 
384             for (IEnumerator e = this.GetEnumerator(); e.MoveNext();)
385                 array.SetValue(e.Current, index++);
386         }
387 
388         Object ICollection.SyncRoot
389         {
390             get
391             {
392                 if (_syncRoot == null)
393                 {
394                     System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null);
395                 }
396                 return _syncRoot;
397             }
398         }
399 
400         bool ICollection.IsSynchronized
401         {
402             get { return false; }
403         }
404 
405         //
406         //  Helper methods to get arrays of keys and values
407         //
408 
409         /// <devdoc>
410         /// <para>Returns a <see cref='System.String' qualify='true'/> array containing all the keys in the
411         /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
412         /// </devdoc>
BaseGetAllKeys()413         protected String[] BaseGetAllKeys()
414         {
415             int n = _entriesArray.Count;
416             String[] allKeys = new String[n];
417 
418             for (int i = 0; i < n; i++)
419                 allKeys[i] = BaseGetKey(i);
420 
421             return allKeys;
422         }
423 
424         /// <devdoc>
425         /// <para>Returns an <see cref='System.Object' qualify='true'/> array containing all the values in the
426         /// <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
427         /// </devdoc>
BaseGetAllValues()428         protected Object[] BaseGetAllValues()
429         {
430             int n = _entriesArray.Count;
431             Object[] allValues = new Object[n];
432 
433             for (int i = 0; i < n; i++)
434                 allValues[i] = BaseGet(i);
435 
436             return allValues;
437         }
438 
439         /// <devdoc>
440         ///    <para>Returns an array of the specified type containing
441         ///       all the values in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
442         /// </devdoc>
BaseGetAllValues(Type type)443         protected object[] BaseGetAllValues(Type type)
444         {
445             int n = _entriesArray.Count;
446             if (type == null)
447             {
448                 throw new ArgumentNullException(nameof(type));
449             }
450             object[] allValues = (object[])Array.CreateInstance(type, n);
451 
452             for (int i = 0; i < n; i++)
453             {
454                 allValues[i] = BaseGet(i);
455             }
456 
457             return allValues;
458         }
459 
460         //
461         // Keys property
462         //
463 
464         /// <devdoc>
465         /// <para>Returns a <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/> instance containing
466         ///    all the keys in the <see cref='System.Collections.Specialized.NameObjectCollectionBase'/> instance.</para>
467         /// </devdoc>
468         public virtual KeysCollection Keys
469         {
470             get
471             {
472                 if (_keys == null)
473                     _keys = new KeysCollection(this);
474                 return _keys;
475             }
476         }
477 
478         //
479         // Simple entry class to allow substitution of values and indexed access to keys
480         //
481 
482         internal class NameObjectEntry
483         {
NameObjectEntry(String name, Object value)484             internal NameObjectEntry(String name, Object value)
485             {
486                 Key = name;
487                 Value = value;
488             }
489 
490             internal String Key;
491             internal Object Value;
492         }
493 
494         //
495         // Enumerator over keys of NameObjectCollection
496         //
497 
498         [Serializable]
499         internal class NameObjectKeysEnumerator : IEnumerator
500         {
501             private int _pos;
502             private NameObjectCollectionBase _coll;
503             private int _version;
504 
NameObjectKeysEnumerator(NameObjectCollectionBase coll)505             internal NameObjectKeysEnumerator(NameObjectCollectionBase coll)
506             {
507                 _coll = coll;
508                 _version = _coll._version;
509                 _pos = -1;
510             }
511 
MoveNext()512             public bool MoveNext()
513             {
514                 if (_version != _coll._version)
515                     throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
516 
517                 if (_pos < _coll.Count - 1)
518                 {
519                     _pos++;
520                     return true;
521                 }
522                 else
523                 {
524                     _pos = _coll.Count;
525                     return false;
526                 }
527             }
528 
Reset()529             public void Reset()
530             {
531                 if (_version != _coll._version)
532                     throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion);
533                 _pos = -1;
534             }
535 
536             public Object Current
537             {
538                 get
539                 {
540                     if (_pos >= 0 && _pos < _coll.Count)
541                     {
542                         return _coll.BaseGetKey(_pos);
543                     }
544                     else
545                     {
546                         throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen);
547                     }
548                 }
549             }
550         }
551 
552         //
553         // Keys collection
554         //
555 
556         /// <devdoc>
557         /// <para>Represents a collection of the <see cref='System.String' qualify='true'/> keys of a collection.</para>
558         /// </devdoc>
559         [Serializable]
560         public class KeysCollection : ICollection
561         {
562             private NameObjectCollectionBase _coll;
563 
KeysCollection(NameObjectCollectionBase coll)564             internal KeysCollection(NameObjectCollectionBase coll)
565             {
566                 _coll = coll;
567             }
568 
569             // Indexed access
570 
571             /// <devdoc>
572             ///    <para> Gets the key at the specified index of the collection.</para>
573             /// </devdoc>
Get(int index)574             public virtual String Get(int index)
575             {
576                 return _coll.BaseGetKey(index);
577             }
578 
579             /// <devdoc>
580             ///    <para>Represents the entry at the specified index of the collection.</para>
581             /// </devdoc>
582             public String this[int index]
583             {
584                 get
585                 {
586                     return Get(index);
587                 }
588             }
589 
590             // ICollection implementation
591 
592             /// <devdoc>
593             ///    <para>Returns an enumerator that can iterate through the
594             ///    <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/>.</para>
595             /// </devdoc>
GetEnumerator()596             public IEnumerator GetEnumerator()
597             {
598                 return new NameObjectKeysEnumerator(_coll);
599             }
600 
601             /// <devdoc>
602             /// <para>Gets the number of keys in the <see cref='System.Collections.Specialized.NameObjectCollectionBase.KeysCollection'/>.</para>
603             /// </devdoc>
604             public int Count
605             {
606                 get
607                 {
608                     return _coll.Count;
609                 }
610             }
611 
ICollection.CopyTo(Array array, int index)612             void ICollection.CopyTo(Array array, int index)
613             {
614                 if (array == null)
615                 {
616                     throw new ArgumentNullException(nameof(array));
617                 }
618 
619                 if (array.Rank != 1)
620                 {
621                     throw new ArgumentException(SR.Arg_MultiRank, nameof(array));
622                 }
623 
624                 if (index < 0)
625                 {
626                     throw new ArgumentOutOfRangeException(nameof(index), index, SR.ArgumentOutOfRange_NeedNonNegNum);
627                 }
628 
629                 if (array.Length - index < _coll.Count)
630                 {
631                     throw new ArgumentException(SR.Arg_InsufficientSpace);
632                 }
633 
634                 for (IEnumerator e = this.GetEnumerator(); e.MoveNext();)
635                     array.SetValue(e.Current, index++);
636             }
637 
638             Object ICollection.SyncRoot
639             {
640                 get { return ((ICollection)_coll).SyncRoot; }
641             }
642 
643 
644             bool ICollection.IsSynchronized
645             {
646                 get { return false; }
647             }
648         }
649     }
650 }
651