File: UI\StateManagedCollection.cs
Project: ndp\fx\src\xsp\system\Web\System.Web.csproj (System.Web)
//------------------------------------------------------------------------------
// <copyright file="StateManagedCollection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
 
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;
 
    /// <devdoc>
    /// 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.
    /// </devdoc>
    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;
 
 
 
        /// <devdoc>
        /// Creates a new instance of StateManagedCollection.
        /// </devdoc>
        protected StateManagedCollection() {
            _collectionItems = new ArrayList();
        }
 
 
 
        /// <devdoc>
        /// Returns the number of items in the collection.
        /// </devdoc>
        public int Count {
            get {
                return _collectionItems.Count;
            }
        }
 
 
        /// <devdoc>
        /// Removes all the items from the collection.
        /// </devdoc>
        public void Clear() {
            OnClear();
            _collectionItems.Clear();
            OnClearComplete();
 
            if (_tracking) {
                _saveAll = true;
            }
        }
 
 
        public void CopyTo(Array array, int index) {
            _collectionItems.CopyTo(array, index);
        }
 
 
        /// <devdoc>
        /// 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().
        /// </devdoc>
        protected virtual object CreateKnownType(int index) {
            throw new InvalidOperationException(SR.GetString(SR.StateManagedCollection_NoKnownTypes));
        }
 
 
        /// <devdoc>
        /// Returns the IEnumerator for the collection.
        /// </devdoc>
        public IEnumerator GetEnumerator() {
            return _collectionItems.GetEnumerator();
        }
 
 
        /// <devdoc>
        /// Returns an ordered list of known types.
        /// </devdoc>
        protected virtual Type[] GetKnownTypes() {
            return null;
        }
 
        /// <devdoc>
        /// Returns the number of known types.
        /// </devdoc>
        private int GetKnownTypeCount() {
            Type[] types = GetKnownTypes();
 
            if (types == null)
                return 0;
 
            return types.Length;
        }
 
        /// <devdoc>
        /// 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.
        /// </devdoc>
        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;
            }
        }
 
        /// <devdoc>
        /// Loads all items from view state.
        /// </devdoc>
        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);
                }
            }
        }
 
        /// <devdoc>
        /// Loads only changed items from view state.
        /// </devdoc>
        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);
                    }
                }
            }
        }
 
 
        /// <devdoc>
        /// Called when the Clear() method is starting.
        /// </devdoc>
        protected virtual void OnClear() {
        }
 
 
        /// <devdoc>
        /// Called when the Clear() method is complete.
        /// </devdoc>
        protected virtual void OnClearComplete() {
        }
 
 
        /// <devdoc>
        /// Called when an object must be validated.
        /// </devdoc>
        protected virtual void OnValidate(object value) {
            if (value == null) throw new ArgumentNullException("value");
        }
 
 
        /// <devdoc>
        /// Called when the Insert() method is starting.
        /// </devdoc>
        protected virtual void OnInsert(int index, object value) {
        }
 
 
        /// <devdoc>
        /// Called when the Insert() method is complete.
        /// </devdoc>
        protected virtual void OnInsertComplete(int index, object value) {
        }
 
 
        /// <devdoc>
        /// Called when the Remove() method is starting.
        /// </devdoc>
        protected virtual void OnRemove(int index, object value) {
        }
 
 
        /// <devdoc>
        /// Called when the Remove() method is complete.
        /// </devdoc>
        protected virtual void OnRemoveComplete(int index, object value) {
        }
 
        /// <devdoc>
        /// Saves all items in the collection to view state.
        /// </devdoc>
        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));
            }
        }
 
        /// <devdoc>
        /// Saves changed items to view state.
        /// </devdoc>
        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));
            }
        }
 
        /// <devdoc>
        /// 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.
        /// </devdoc>
        public void SetDirty() {
            _saveAll = true;
        }
 
 
        /// <devdoc>
        /// Flags an object to record its entire state instead of just changed parts.
        /// </devdoc>
        protected abstract void SetDirtyObject(object o);
 
 
 
        /// <internalonly/>
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }
 
 
 
        /// <internalonly/>
        int ICollection.Count {
            get {
                return Count;
            }
        }
 
 
        /// <internalonly/>
        bool ICollection.IsSynchronized {
            get {
                return false;
            }
        }
 
 
        /// <internalonly/>
        object ICollection.SyncRoot {
            get {
                return null;
            }
        }
 
 
        /// <internalonly/>
        bool IList.IsFixedSize {
            get {
                return false;
            }
        }
 
 
        /// <internalonly/>
        bool IList.IsReadOnly {
            get {
                return _collectionItems.IsReadOnly;
            }
        }
 
 
        /// <internalonly/>
        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);
            }
        }
 
 
        /// <internalonly/>
        int IList.Add(object value) {
            OnValidate(value);
 
            InsertInternal(-1, value);
 
            return _collectionItems.Count - 1;
        }
 
 
        /// <internalonly/>
        void IList.Clear() {
            Clear();
        }
 
 
        /// <internalonly/>
        bool IList.Contains(object value) {
            if (value == null) {
                return false;
            }
 
            OnValidate(value);
 
            return _collectionItems.Contains(value);
        }
 
 
        /// <internalonly/>
        int IList.IndexOf(object value) {
            if (value == null) {
                return -1;
            }
 
            OnValidate(value);
 
            return _collectionItems.IndexOf(value);
        }
 
 
        /// <internalonly/>
        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;
            }
        }
 
 
        /// <internalonly/>
        void IList.Remove(object value) {
            if (value == null) {
                return;
            }
 
            OnValidate(value);
 
            ((IList)this).RemoveAt(((IList)this).IndexOf(value));
        }
 
 
        /// <internalonly/>
        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;
            }
        }
 
 
 
        /// <internalonly/>
        bool IStateManager.IsTrackingViewState {
            get {
                return _tracking;
            }
        }
 
 
        /// <internalonly/>
        void IStateManager.LoadViewState(object savedState) {
 
            if (savedState != null) {
                if (savedState is Triplet) {
                    LoadChangedItemsFromViewState(savedState);
                }
                else {
                    LoadAllItemsFromViewState(savedState);
                }
            }
        }
 
 
        /// <internalonly/>
        object IStateManager.SaveViewState() {
 
            if (_saveAll) {
                return SaveAllItemsToViewState();
            }
            else {
                return SaveChangedItemsToViewState();
            }
        }
 
 
        /// <internalonly/>
        void IStateManager.TrackViewState() {
            if (!((IStateManager)this).IsTrackingViewState) {
                _hadItems = Count > 0;
 
                _tracking = true;
 
                foreach (IStateManager o in _collectionItems) {
                    o.TrackViewState();
                }
            }
        }
    }
}