//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Web.UI { using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.ComponentModel.Design; using System.Drawing.Design; using System.Reflection; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.Util; /// /// Manages state for an arbitrary collection of items that implement IStateManager. /// The collection differentiates between known types and unknown types. /// Known types take up less space in ViewState because only an index needs to be stored instead of a fully qualified type name. /// Unknown types need to have their fully qualified type name stored in ViewState so they take up more space. /// public abstract class StateManagedCollection : IList, IStateManager { private ArrayList _collectionItems; private bool _tracking; private bool _saveAll; // We want to know if the collection had items to begin with // so we don't put empty collections in the ViewState unnecessarily private bool _hadItems; /// /// Creates a new instance of StateManagedCollection. /// protected StateManagedCollection() { _collectionItems = new ArrayList(); } /// /// Returns the number of items in the collection. /// public int Count { get { return _collectionItems.Count; } } /// /// Removes all the items from the collection. /// public void Clear() { OnClear(); _collectionItems.Clear(); OnClearComplete(); if (_tracking) { _saveAll = true; } } public void CopyTo(Array array, int index) { _collectionItems.CopyTo(array, index); } /// /// Creates an object of a known type based on an index into an array of types. /// Indexes passed into CreateKnownType() must mach the indexes of the ArrayList returned /// by GetKnownTypes(). /// protected virtual object CreateKnownType(int index) { throw new InvalidOperationException(SR.GetString(SR.StateManagedCollection_NoKnownTypes)); } /// /// Returns the IEnumerator for the collection. /// public IEnumerator GetEnumerator() { return _collectionItems.GetEnumerator(); } /// /// Returns an ordered list of known types. /// protected virtual Type[] GetKnownTypes() { return null; } /// /// Returns the number of known types. /// private int GetKnownTypeCount() { Type[] types = GetKnownTypes(); if (types == null) return 0; return types.Length; } /// /// Inserts a new object into the collection at a given index. /// If the index is -1 then the object is appended to the end of the collection. /// private void InsertInternal(int index, object o) { Debug.Assert(index >= -1 && index <= Count, "Expected index to be at least -1 and less than or equal to Count."); if (o == null) { throw new ArgumentNullException("o"); } if (((IStateManager)this).IsTrackingViewState) { ((IStateManager)o).TrackViewState(); SetDirtyObject(o); } OnInsert(index, o); int trueIndex; if (index == -1) { trueIndex = _collectionItems.Add(o); } else { trueIndex = index; _collectionItems.Insert(index, o); } try { OnInsertComplete(index, o); } catch { _collectionItems.RemoveAt(trueIndex); throw; } } /// /// Loads all items from view state. /// private void LoadAllItemsFromViewState(object savedState) { Debug.Assert(savedState != null); Debug.Assert(savedState is Pair); Pair p1 = (Pair)savedState; if (p1.Second is Pair) { Pair p2 = (Pair)p1.Second; // save all mode; some objects are typed object[] states = (object[])p1.First; int[] typeIndices = (int[])p2.First; ArrayList typedObjectTypeNames = (ArrayList)p2.Second; Clear(); for (int i = 0; i < states.Length; i++) { object o; // If there is only one known type, we don't need type indices if (typeIndices == null) { // Create known type o = CreateKnownType(0); } else { int typeIndex = typeIndices[i]; if (typeIndex < GetKnownTypeCount()) { // Create known type o = CreateKnownType(typeIndex); } else { string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()]; Type type = Type.GetType(typeName); o = Activator.CreateInstance(type); } } ((IStateManager)o).TrackViewState(); ((IStateManager)o).LoadViewState(states[i]); ((IList)this).Add(o); } } else { Debug.Assert(p1.First is object[]); // save all mode; all objects are instances of known types object[] states = (object[])p1.First; int[] typeIndices = (int[])p1.Second; Clear(); for (int i = 0; i < states.Length; i++) { // Create known type int typeIndex = 0; if (typeIndices != null) { typeIndex = (int)typeIndices[i]; } object o = CreateKnownType(typeIndex); ((IStateManager)o).TrackViewState(); ((IStateManager)o).LoadViewState(states[i]); ((IList)this).Add(o); } } } /// /// Loads only changed items from view state. /// private void LoadChangedItemsFromViewState(object savedState) { Debug.Assert(savedState != null); Debug.Assert(savedState is Triplet); Triplet t = (Triplet)savedState; if (t.Third is Pair) { // save some mode; some new objects are typed Pair p = (Pair)t.Third; ArrayList indices = (ArrayList)t.First; ArrayList states = (ArrayList)t.Second; ArrayList typeIndices = (ArrayList)p.First; ArrayList typedObjectTypeNames = (ArrayList)p.Second; for (int i = 0; i < indices.Count; i++) { int index = (int)indices[i]; if (index < Count) { ((IStateManager)((IList)this)[index]).LoadViewState(states[i]); } else { object o; // If there is only one known type, we don't need type indices if (typeIndices == null) { // Create known type o = CreateKnownType(0); } else { int typeIndex = (int)typeIndices[i]; if (typeIndex < GetKnownTypeCount()) { // Create known type o = CreateKnownType(typeIndex); } else { string typeName = (string)typedObjectTypeNames[typeIndex - GetKnownTypeCount()]; Type type = Type.GetType(typeName); o = Activator.CreateInstance(type); } } ((IStateManager)o).TrackViewState(); ((IStateManager)o).LoadViewState(states[i]); ((IList)this).Add(o); } } } else { // save some mode; all new objects are instances of known types ArrayList indices = (ArrayList)t.First; ArrayList states = (ArrayList)t.Second; ArrayList typeIndices = (ArrayList)t.Third; for (int i = 0; i < indices.Count; i++) { int index = (int)indices[i]; if (index < Count) { ((IStateManager)((IList)this)[index]).LoadViewState(states[i]); } else { // Create known type int typeIndex = 0; if (typeIndices != null) { typeIndex = (int)typeIndices[i]; } object o = CreateKnownType(typeIndex); ((IStateManager)o).TrackViewState(); ((IStateManager)o).LoadViewState(states[i]); ((IList)this).Add(o); } } } } /// /// Called when the Clear() method is starting. /// protected virtual void OnClear() { } /// /// Called when the Clear() method is complete. /// protected virtual void OnClearComplete() { } /// /// Called when an object must be validated. /// protected virtual void OnValidate(object value) { if (value == null) throw new ArgumentNullException("value"); } /// /// Called when the Insert() method is starting. /// protected virtual void OnInsert(int index, object value) { } /// /// Called when the Insert() method is complete. /// protected virtual void OnInsertComplete(int index, object value) { } /// /// Called when the Remove() method is starting. /// protected virtual void OnRemove(int index, object value) { } /// /// Called when the Remove() method is complete. /// protected virtual void OnRemoveComplete(int index, object value) { } /// /// Saves all items in the collection to view state. /// private object SaveAllItemsToViewState() { Debug.Assert(_saveAll); bool hasState = false; int count = _collectionItems.Count; int[] typeIndices = new int[count]; object[] states = new object[count]; ArrayList typedObjectTypeNames = null; IDictionary typedObjectTracker = null; int knownTypeCount = GetKnownTypeCount(); for (int i = 0; i < count; i++) { object o = _collectionItems[i]; SetDirtyObject(o); states[i] = ((IStateManager)o).SaveViewState(); if (states[i] != null) hasState = true; Type objectType = o.GetType(); int knownTypeIndex = -1; // If there are known types, find index if (knownTypeCount != 0) { knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType); } if (knownTypeIndex != -1) { // Type is known typeIndices[i] = knownTypeIndex; } else { // Type is unknown if (typedObjectTypeNames == null) { typedObjectTypeNames = new ArrayList(); typedObjectTracker = new HybridDictionary(); } // Get index of type object index = typedObjectTracker[objectType]; // If type is not in list, add it to the list if (index == null) { typedObjectTypeNames.Add(objectType.AssemblyQualifiedName); // Offset the index by the known type count index = typedObjectTypeNames.Count + knownTypeCount - 1; typedObjectTracker[objectType] = index; } typeIndices[i] = (int)index; } } // If the collection didn't have items to begin with don't save the state if (!_hadItems && !hasState) { return null; } if (typedObjectTypeNames == null) { // all objects are instances known types // If there is only one known type, then all objects are of that type so the indices are not needed if (knownTypeCount == 1) typeIndices = null; return new Pair(states, typeIndices); } else { return new Pair(states, new Pair(typeIndices, typedObjectTypeNames)); } } /// /// Saves changed items to view state. /// private object SaveChangedItemsToViewState() { Debug.Assert(_saveAll == false); bool hasState = false; int count = _collectionItems.Count; ArrayList indices = new ArrayList(); ArrayList states = new ArrayList(); ArrayList typeIndices = new ArrayList(); ArrayList typedObjectTypeNames = null; IDictionary typedObjectTracker = null; int knownTypeCount = GetKnownTypeCount(); for (int i = 0; i < count; i++) { object o = _collectionItems[i]; object state = ((IStateManager)o).SaveViewState(); if (state != null) { hasState = true; indices.Add(i); states.Add(state); Type objectType = o.GetType(); int knownTypeIndex = -1; // If there are known types, find index if (knownTypeCount != 0) { knownTypeIndex = ((IList)GetKnownTypes()).IndexOf(objectType); } if (knownTypeIndex != -1) { // Type is known typeIndices.Add(knownTypeIndex); } else { // Type is unknown if (typedObjectTypeNames == null) { typedObjectTypeNames = new ArrayList(); typedObjectTracker = new HybridDictionary(); } object index = typedObjectTracker[objectType]; if (index == null) { typedObjectTypeNames.Add(objectType.AssemblyQualifiedName); // Offset the index by the known type count index = typedObjectTypeNames.Count + knownTypeCount - 1; typedObjectTracker[objectType] = index; } typeIndices.Add(index); } } } // If the collection didn't have items to begin with don't save the state if (!_hadItems && !hasState) { return null; } if (typedObjectTypeNames == null) { // all objects are instances known types // If there is only one known type, then all objects are of that type so the indices are not needed if (knownTypeCount == 1) typeIndices = null; return new Triplet(indices, states, typeIndices); } else { return new Triplet(indices, states, new Pair(typeIndices, typedObjectTypeNames)); } } /// /// Forces the entire collection to be serialized into viewstate, not just /// the change-information. This is useful when a collection has changed in /// a significant way and change information would be insufficient to /// recreate the object. /// public void SetDirty() { _saveAll = true; } /// /// Flags an object to record its entire state instead of just changed parts. /// protected abstract void SetDirtyObject(object o); /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// int ICollection.Count { get { return Count; } } /// bool ICollection.IsSynchronized { get { return false; } } /// object ICollection.SyncRoot { get { return null; } } /// bool IList.IsFixedSize { get { return false; } } /// bool IList.IsReadOnly { get { return _collectionItems.IsReadOnly; } } /// object IList.this[int index] { get { return _collectionItems[index]; } set { if (index < 0 || index >= Count) { throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex)); } ((IList)this).RemoveAt(index); ((IList)this).Insert(index, value); } } /// int IList.Add(object value) { OnValidate(value); InsertInternal(-1, value); return _collectionItems.Count - 1; } /// void IList.Clear() { Clear(); } /// bool IList.Contains(object value) { if (value == null) { return false; } OnValidate(value); return _collectionItems.Contains(value); } /// int IList.IndexOf(object value) { if (value == null) { return -1; } OnValidate(value); return _collectionItems.IndexOf(value); } /// void IList.Insert(int index, object value) { if (value == null) { throw new ArgumentNullException("value"); } if (index < 0 || index > Count) { throw new ArgumentOutOfRangeException("index", SR.GetString(SR.StateManagedCollection_InvalidIndex)); } OnValidate(value); InsertInternal(index, value); if (_tracking) { _saveAll = true; } } /// void IList.Remove(object value) { if (value == null) { return; } OnValidate(value); ((IList)this).RemoveAt(((IList)this).IndexOf(value)); } /// void IList.RemoveAt(int index) { object o = _collectionItems[index]; OnRemove(index, o); _collectionItems.RemoveAt(index); try { OnRemoveComplete(index, o); } catch { _collectionItems.Insert(index, o); throw; } if (_tracking) { _saveAll = true; } } /// bool IStateManager.IsTrackingViewState { get { return _tracking; } } /// void IStateManager.LoadViewState(object savedState) { if (savedState != null) { if (savedState is Triplet) { LoadChangedItemsFromViewState(savedState); } else { LoadAllItemsFromViewState(savedState); } } } /// object IStateManager.SaveViewState() { if (_saveAll) { return SaveAllItemsToViewState(); } else { return SaveChangedItemsToViewState(); } } /// void IStateManager.TrackViewState() { if (!((IStateManager)this).IsTrackingViewState) { _hadItems = Count > 0; _tracking = true; foreach (IStateManager o in _collectionItems) { o.TrackViewState(); } } } } }