1 //------------------------------------------------------------------------------
2 // <copyright file="StateManagedCollection.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Web.UI {
8 
9     using System;
10     using System.Collections;
11     using System.Collections.Specialized;
12     using System.ComponentModel;
13     using System.ComponentModel.Design;
14     using System.Drawing.Design;
15     using System.Reflection;
16     using System.Web.UI;
17     using System.Web.UI.WebControls;
18     using System.Web.Util;
19 
20     /// <devdoc>
21     /// Manages state for an arbitrary collection of items that implement IStateManager.
22     /// The collection differentiates between known types and unknown types.
23     /// Known types take up less space in ViewState because only an index needs to be stored instead of a fully qualified type name.
24     /// Unknown types need to have their fully qualified type name stored in ViewState so they take up more space.
25     /// </devdoc>
26     public abstract class StateManagedCollection : IList, IStateManager {
27 
28         private ArrayList _collectionItems;
29 
30         private bool _tracking;
31         private bool _saveAll;
32 
33         // We want to know if the collection had items to begin with
34         // so we don't put empty collections in the ViewState unnecessarily
35         private bool _hadItems;
36 
37 
38 
39         /// <devdoc>
40         /// Creates a new instance of StateManagedCollection.
41         /// </devdoc>
StateManagedCollection()42         protected StateManagedCollection() {
43             _collectionItems = new ArrayList();
44         }
45 
46 
47 
48         /// <devdoc>
49         /// Returns the number of items in the collection.
50         /// </devdoc>
51         public int Count {
52             get {
53                 return _collectionItems.Count;
54             }
55         }
56 
57 
58         /// <devdoc>
59         /// Removes all the items from the collection.
60         /// </devdoc>
Clear()61         public void Clear() {
62             OnClear();
63             _collectionItems.Clear();
64             OnClearComplete();
65 
66             if (_tracking) {
67                 _saveAll = true;
68             }
69         }
70 
71 
CopyTo(Array array, int index)72         public void CopyTo(Array array, int index) {
73             _collectionItems.CopyTo(array, index);
74         }
75 
76 
77         /// <devdoc>
78         /// Creates an object of a known type based on an index into an array of types.
79         /// Indexes passed into CreateKnownType() must mach the indexes of the ArrayList returned
80         /// by GetKnownTypes().
81         /// </devdoc>
CreateKnownType(int index)82         protected virtual object CreateKnownType(int index) {
83             throw new InvalidOperationException(SR.GetString(SR.StateManagedCollection_NoKnownTypes));
84         }
85 
86 
87         /// <devdoc>
88         /// Returns the IEnumerator for the collection.
89         /// </devdoc>
GetEnumerator()90         public IEnumerator GetEnumerator() {
91             return _collectionItems.GetEnumerator();
92         }
93 
94 
95         /// <devdoc>
96         /// Returns an ordered list of known types.
97         /// </devdoc>
GetKnownTypes()98         protected virtual Type[] GetKnownTypes() {
99             return null;
100         }
101 
102         /// <devdoc>
103         /// Returns the number of known types.
104         /// </devdoc>
GetKnownTypeCount()105         private int GetKnownTypeCount() {
106             Type[] types = GetKnownTypes();
107 
108             if (types == null)
109                 return 0;
110 
111             return types.Length;
112         }
113 
114         /// <devdoc>
115         /// Inserts a new object into the collection at a given index.
116         /// If the index is -1 then the object is appended to the end of the collection.
117         /// </devdoc>
InsertInternal(int index, object o)118         private void InsertInternal(int index, object o) {
119             Debug.Assert(index >= -1 && index <= Count, "Expected index to be at least -1 and less than or equal to Count.");
120             if (o == null) {
121                 throw new ArgumentNullException("o");
122             }
123 
124             if (((IStateManager)this).IsTrackingViewState) {
125                 ((IStateManager)o).TrackViewState();
126                 SetDirtyObject(o);
127             }
128 
129             OnInsert(index, o);
130 
131             int trueIndex;
132 
133             if (index == -1) {
134                 trueIndex = _collectionItems.Add(o);
135             }
136             else {
137                 trueIndex = index;
138                 _collectionItems.Insert(index, o);
139             }
140 
141             try {
142                 OnInsertComplete(index, o);
143             }
144             catch {
145                 _collectionItems.RemoveAt(trueIndex);
146                 throw;
147             }
148         }
149 
150         /// <devdoc>
151         /// Loads all items from view state.
152         /// </devdoc>
LoadAllItemsFromViewState(object savedState)153         private void LoadAllItemsFromViewState(object savedState) {
154             Debug.Assert(savedState != null);
155             Debug.Assert(savedState is Pair);
156 
157             Pair p1 = (Pair)savedState;
158 
159             if (p1.Second is Pair) {
160                 Pair p2 = (Pair)p1.Second;
161 
162                 // save all mode; some objects are typed
163                 object[] states = (object[])p1.First;
164                 int[] typeIndices = (int[])p2.First;
165                 ArrayList typedObjectTypeNames = (ArrayList)p2.Second;
166 
167                 Clear();
168 
169                 for (int i = 0; i < states.Length; i++) {
170                     object o;
171 
172                     // If there is only one known type, we don't need type indices
173                     if (typeIndices == null) {
174                         // Create known type
175                         o = CreateKnownType(0);
176                     }
177                     else {
178                         int typeIndex = typeIndices[i];
179 
180                         if (typeIndex < GetKnownTypeCount()) {
181                             // Create known type
182                             o = CreateKnownType(typeIndex);
183                         }
184                         else {
185                             string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()];
186                             Type type = Type.GetType(typeName);
187 
188                             o = Activator.CreateInstance(type);
189                         }
190                     }
191 
192                     ((IStateManager)o).TrackViewState();
193                     ((IStateManager)o).LoadViewState(states[i]);
194 
195                     ((IList)this).Add(o);
196                 }
197             }
198             else {
199                 Debug.Assert(p1.First is object[]);
200 
201                 // save all mode; all objects are instances of known types
202                 object[] states = (object[])p1.First;
203                 int[] typeIndices = (int[])p1.Second;
204 
205                 Clear();
206 
207                 for (int i = 0; i < states.Length; i++) {
208                     // Create known type
209                     int typeIndex = 0;
210                     if (typeIndices != null) {
211                         typeIndex = (int)typeIndices[i];
212                     }
213                     object o = CreateKnownType(typeIndex);
214 
215                     ((IStateManager)o).TrackViewState();
216                     ((IStateManager)o).LoadViewState(states[i]);
217 
218                     ((IList)this).Add(o);
219                 }
220             }
221         }
222 
223         /// <devdoc>
224         /// Loads only changed items from view state.
225         /// </devdoc>
LoadChangedItemsFromViewState(object savedState)226         private void LoadChangedItemsFromViewState(object savedState) {
227             Debug.Assert(savedState != null);
228             Debug.Assert(savedState is Triplet);
229 
230             Triplet t = (Triplet)savedState;
231 
232             if (t.Third is Pair) {
233                 // save some mode; some new objects are typed
234                 Pair p = (Pair)t.Third;
235 
236                 ArrayList indices = (ArrayList)t.First;
237                 ArrayList states = (ArrayList)t.Second;
238                 ArrayList typeIndices = (ArrayList)p.First;
239                 ArrayList typedObjectTypeNames = (ArrayList)p.Second;
240 
241                 for (int i = 0; i < indices.Count; i++) {
242                     int index = (int)indices[i];
243 
244                     if (index < Count) {
245                         ((IStateManager)((IList)this)[index]).LoadViewState(states[i]);
246                     }
247                     else {
248                         object o;
249 
250                         // If there is only one known type, we don't need type indices
251                         if (typeIndices == null) {
252                             // Create known type
253                             o = CreateKnownType(0);
254                         }
255                         else {
256                             int typeIndex = (int)typeIndices[i];
257 
258                             if (typeIndex < GetKnownTypeCount()) {
259                                 // Create known type
260                                 o = CreateKnownType(typeIndex);
261                             }
262                             else {
263                                 string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()];
264                                 Type type = Type.GetType(typeName);
265 
266                                 o = Activator.CreateInstance(type);
267                             }
268                         }
269 
270                         ((IStateManager)o).TrackViewState();
271                         ((IStateManager)o).LoadViewState(states[i]);
272 
273                         ((IList)this).Add(o);
274                     }
275                 }
276             }
277             else {
278                 // save some mode; all new objects are instances of known types
279                 ArrayList indices = (ArrayList)t.First;
280                 ArrayList states = (ArrayList)t.Second;
281                 ArrayList typeIndices = (ArrayList)t.Third;
282 
283                 for (int i = 0; i < indices.Count; i++) {
284                     int index = (int)indices[i];
285 
286                     if (index < Count) {
287                         ((IStateManager)((IList)this)[index]).LoadViewState(states[i]);
288                     }
289                     else {
290                         // Create known type
291                         int typeIndex = 0;
292                         if (typeIndices != null) {
293                             typeIndex = (int)typeIndices[i];
294                         }
295                         object o = CreateKnownType(typeIndex);
296 
297                         ((IStateManager)o).TrackViewState();
298                         ((IStateManager)o).LoadViewState(states[i]);
299 
300                         ((IList)this).Add(o);
301                     }
302                 }
303             }
304         }
305 
306 
307         /// <devdoc>
308         /// Called when the Clear() method is starting.
309         /// </devdoc>
OnClear()310         protected virtual void OnClear() {
311         }
312 
313 
314         /// <devdoc>
315         /// Called when the Clear() method is complete.
316         /// </devdoc>
OnClearComplete()317         protected virtual void OnClearComplete() {
318         }
319 
320 
321         /// <devdoc>
322         /// Called when an object must be validated.
323         /// </devdoc>
OnValidate(object value)324         protected virtual void OnValidate(object value) {
325             if (value == null) throw new ArgumentNullException("value");
326         }
327 
328 
329         /// <devdoc>
330         /// Called when the Insert() method is starting.
331         /// </devdoc>
OnInsert(int index, object value)332         protected virtual void OnInsert(int index, object value) {
333         }
334 
335 
336         /// <devdoc>
337         /// Called when the Insert() method is complete.
338         /// </devdoc>
OnInsertComplete(int index, object value)339         protected virtual void OnInsertComplete(int index, object value) {
340         }
341 
342 
343         /// <devdoc>
344         /// Called when the Remove() method is starting.
345         /// </devdoc>
OnRemove(int index, object value)346         protected virtual void OnRemove(int index, object value) {
347         }
348 
349 
350         /// <devdoc>
351         /// Called when the Remove() method is complete.
352         /// </devdoc>
OnRemoveComplete(int index, object value)353         protected virtual void OnRemoveComplete(int index, object value) {
354         }
355 
356         /// <devdoc>
357         /// Saves all items in the collection to view state.
358         /// </devdoc>
SaveAllItemsToViewState()359         private object SaveAllItemsToViewState() {
360             Debug.Assert(_saveAll);
361 
362             bool hasState = false;
363 
364             int count = _collectionItems.Count;
365 
366             int[] typeIndices = new int[count];
367             object[] states = new object[count];
368 
369             ArrayList typedObjectTypeNames = null;
370             IDictionary typedObjectTracker = null;
371 
372             int knownTypeCount = GetKnownTypeCount();
373 
374 
375             for (int i = 0; i < count; i++) {
376                 object o = _collectionItems[i];
377                 SetDirtyObject(o);
378 
379                 states[i] = ((IStateManager)o).SaveViewState();
380 
381                 if (states[i] != null)
382                     hasState = true;
383 
384                 Type objectType = o.GetType();
385 
386                 int knownTypeIndex = -1;
387 
388                 // If there are known types, find index
389                 if (knownTypeCount != 0) {
390                     knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType);
391                 }
392 
393                 if (knownTypeIndex != -1) {
394                     // Type is known
395                     typeIndices[i] = knownTypeIndex;
396                 }
397                 else {
398                     // Type is unknown
399                     if (typedObjectTypeNames == null) {
400                         typedObjectTypeNames = new ArrayList();
401                         typedObjectTracker = new HybridDictionary();
402                     }
403 
404                     // Get index of type
405                     object index = typedObjectTracker[objectType];
406 
407                     // If type is not in list, add it to the list
408                     if (index == null) {
409                         typedObjectTypeNames.Add(objectType.AssemblyQualifiedName);
410 
411                         // Offset the index by the known type count
412                         index = typedObjectTypeNames.Count + knownTypeCount - 1;
413                         typedObjectTracker[objectType] = index;
414                     }
415 
416                     typeIndices[i] = (int)index;
417                 }
418             }
419 
420             // If the collection didn't have items to begin with don't save the state
421             if (!_hadItems && !hasState) {
422                 return null;
423             }
424 
425             if (typedObjectTypeNames == null) {
426                 // all objects are instances known types
427 
428                 // If there is only one known type, then all objects are of that type so the indices are not needed
429                 if (knownTypeCount == 1)
430                     typeIndices = null;
431 
432                 return new Pair(states, typeIndices);
433             }
434             else {
435                 return new Pair(states, new Pair(typeIndices, typedObjectTypeNames));
436             }
437         }
438 
439         /// <devdoc>
440         /// Saves changed items to view state.
441         /// </devdoc>
SaveChangedItemsToViewState()442         private object SaveChangedItemsToViewState() {
443             Debug.Assert(_saveAll == false);
444 
445             bool hasState = false;
446 
447             int count = _collectionItems.Count;
448 
449             ArrayList indices = new ArrayList();
450             ArrayList states = new ArrayList();
451             ArrayList typeIndices = new ArrayList();
452 
453             ArrayList typedObjectTypeNames = null;
454             IDictionary typedObjectTracker = null;
455 
456             int knownTypeCount = GetKnownTypeCount();
457 
458 
459             for (int i = 0; i < count; i++) {
460                 object o = _collectionItems[i];
461 
462                 object state = ((IStateManager)o).SaveViewState();
463                 if (state != null) {
464                     hasState = true;
465 
466                     indices.Add(i);
467                     states.Add(state);
468 
469                     Type objectType = o.GetType();
470 
471                     int knownTypeIndex = -1;
472 
473                     // If there are known types, find index
474                     if (knownTypeCount != 0) {
475                         knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType);
476                     }
477 
478                     if (knownTypeIndex != -1) {
479                         // Type is known
480                         typeIndices.Add(knownTypeIndex);
481                     }
482                     else {
483                         // Type is unknown
484                         if (typedObjectTypeNames == null) {
485                             typedObjectTypeNames = new ArrayList();
486                             typedObjectTracker = new HybridDictionary();
487                         }
488 
489                         object index = typedObjectTracker[objectType];
490                         if (index == null) {
491                             typedObjectTypeNames.Add(objectType.AssemblyQualifiedName);
492 
493                             // Offset the index by the known type count
494                             index = typedObjectTypeNames.Count + knownTypeCount - 1;
495                             typedObjectTracker[objectType] = index;
496                         }
497 
498                         typeIndices.Add(index);
499                     }
500                 }
501             }
502 
503             // If the collection didn't have items to begin with don't save the state
504             if (!_hadItems && !hasState) {
505                 return null;
506             }
507 
508             if (typedObjectTypeNames == null) {
509                 // all objects are instances known types
510 
511                 // If there is only one known type, then all objects are of that type so the indices are not needed
512                 if (knownTypeCount == 1)
513                     typeIndices = null;
514 
515                 return new Triplet(indices, states, typeIndices);
516             }
517             else {
518                 return new Triplet(indices, states, new Pair(typeIndices, typedObjectTypeNames));
519             }
520         }
521 
522         /// <devdoc>
523         /// Forces the entire collection to be serialized into viewstate, not just
524         /// the change-information. This is useful when a collection has changed in
525         /// a significant way and change information would be insufficient to
526         /// recreate the object.
527         /// </devdoc>
SetDirty()528         public void SetDirty() {
529             _saveAll = true;
530         }
531 
532 
533         /// <devdoc>
534         /// Flags an object to record its entire state instead of just changed parts.
535         /// </devdoc>
SetDirtyObject(object o)536         protected abstract void SetDirtyObject(object o);
537 
538 
539 
540         /// <internalonly/>
IEnumerable.GetEnumerator()541         IEnumerator IEnumerable.GetEnumerator() {
542             return GetEnumerator();
543         }
544 
545 
546 
547         /// <internalonly/>
548         int ICollection.Count {
549             get {
550                 return Count;
551             }
552         }
553 
554 
555         /// <internalonly/>
556         bool ICollection.IsSynchronized {
557             get {
558                 return false;
559             }
560         }
561 
562 
563         /// <internalonly/>
564         object ICollection.SyncRoot {
565             get {
566                 return null;
567             }
568         }
569 
570 
571         /// <internalonly/>
572         bool IList.IsFixedSize {
573             get {
574                 return false;
575             }
576         }
577 
578 
579         /// <internalonly/>
580         bool IList.IsReadOnly {
581             get {
582                 return _collectionItems.IsReadOnly;
583             }
584         }
585 
586 
587         /// <internalonly/>
588         object IList.this[int index] {
589             get {
590                 return _collectionItems[index];
591             }
592             set {
593                 if (index < 0 || index >= Count) {
594                     throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex));
595                 }
596                 ((IList)this).RemoveAt(index);
597                 ((IList)this).Insert(index, value);
598             }
599         }
600 
601 
602         /// <internalonly/>
IList.Add(object value)603         int IList.Add(object value) {
604             OnValidate(value);
605 
606             InsertInternal(-1, value);
607 
608             return _collectionItems.Count - 1;
609         }
610 
611 
612         /// <internalonly/>
IList.Clear()613         void IList.Clear() {
614             Clear();
615         }
616 
617 
618         /// <internalonly/>
IList.Contains(object value)619         bool IList.Contains(object value) {
620             if (value == null) {
621                 return false;
622             }
623 
624             OnValidate(value);
625 
626             return _collectionItems.Contains(value);
627         }
628 
629 
630         /// <internalonly/>
IList.IndexOf(object value)631         int IList.IndexOf(object value) {
632             if (value == null) {
633                 return -1;
634             }
635 
636             OnValidate(value);
637 
638             return _collectionItems.IndexOf(value);
639         }
640 
641 
642         /// <internalonly/>
IList.Insert(int index, object value)643         void IList.Insert(int index, object value) {
644             if (value == null) {
645                 throw new ArgumentNullException("value");
646             }
647             if (index < 0 || index > Count) {
648                 throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex));
649             }
650 
651             OnValidate(value);
652 
653             InsertInternal(index, value);
654 
655             if (_tracking) {
656                 _saveAll = true;
657             }
658         }
659 
660 
661         /// <internalonly/>
IList.Remove(object value)662         void IList.Remove(object value) {
663             if (value == null) {
664                 return;
665             }
666 
667             OnValidate(value);
668 
669             ((IList)this).RemoveAt(((IList)this).IndexOf(value));
670         }
671 
672 
673         /// <internalonly/>
IList.RemoveAt(int index)674         void IList.RemoveAt(int index) {
675             object o = _collectionItems[index];
676 
677             OnRemove(index, o);
678             _collectionItems.RemoveAt(index);
679             try {
680                 OnRemoveComplete(index, o);
681             }
682             catch {
683                 _collectionItems.Insert(index, o);
684                 throw;
685             }
686 
687             if (_tracking) {
688                 _saveAll = true;
689             }
690         }
691 
692 
693 
694         /// <internalonly/>
695         bool IStateManager.IsTrackingViewState {
696             get {
697                 return _tracking;
698             }
699         }
700 
701 
702         /// <internalonly/>
IStateManager.LoadViewState(object savedState)703         void IStateManager.LoadViewState(object savedState) {
704 
705             if (savedState != null) {
706                 if (savedState is Triplet) {
707                     LoadChangedItemsFromViewState(savedState);
708                 }
709                 else {
710                     LoadAllItemsFromViewState(savedState);
711                 }
712             }
713         }
714 
715 
716         /// <internalonly/>
IStateManager.SaveViewState()717         object IStateManager.SaveViewState() {
718 
719             if (_saveAll) {
720                 return SaveAllItemsToViewState();
721             }
722             else {
723                 return SaveChangedItemsToViewState();
724             }
725         }
726 
727 
728         /// <internalonly/>
IStateManager.TrackViewState()729         void IStateManager.TrackViewState() {
730             if (!((IStateManager)this).IsTrackingViewState) {
731                 _hadItems = Count > 0;
732 
733                 _tracking = true;
734 
735                 foreach (IStateManager o in _collectionItems) {
736                     o.TrackViewState();
737                 }
738             }
739         }
740     }
741 }
742 
743