File: System\Xml\Dom\XmlElementList.cs
Project: ndp\fx\src\Xml\System.Xml.csproj (System.Xml)
//------------------------------------------------------------------------------
// <copyright file="XmlElementList.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
 
namespace System.Xml {
    using System;
    using System.Collections;
    using System.Diagnostics;
 
    internal class XmlElementList: XmlNodeList {
        string      asterisk;
        int         changeCount; //recording the total number that the dom tree has been changed ( insertion and deletetion )
        //the member vars below are saved for further reconstruction        
        string      name;         //only one of 2 string groups will be initialized depends on which constructor is called.
        string      localName; 
        string      namespaceURI;
        XmlNode     rootNode;
        // the memeber vars belwo serves the optimization of accessing of the elements in the list
        int         curInd;       // -1 means the starting point for a new search round
        XmlNode     curElem;      // if sets to rootNode, means the starting point for a new search round
        bool        empty;        // whether the list is empty
        bool        atomized;     //whether the localname and namespaceuri are aomized
        int         matchCount;   // cached list count. -1 means it needs reconstruction
 
        WeakReference listener;   // XmlElementListListener
 
        private XmlElementList( XmlNode parent) {
            Debug.Assert ( parent != null );
            Debug.Assert( parent.NodeType == XmlNodeType.Element || parent.NodeType == XmlNodeType.Document );
            this.rootNode = parent;
            Debug.Assert( parent.Document != null );
            this.curInd = -1;
            this.curElem = rootNode;
            this.changeCount = 0;
            this.empty = false;
            this.atomized = true; 
            this.matchCount = -1;
            // This can be a regular reference, but it would cause some kind of loop inside the GC
            this.listener = new WeakReference(new XmlElementListListener(parent.Document, this));
        }
 
        ~XmlElementList() {
            Dispose(false);
        }
 
        internal void ConcurrencyCheck(XmlNodeChangedEventArgs args){
            if( atomized == false ) {
                XmlNameTable nameTable = this.rootNode.Document.NameTable;
                this.localName = nameTable.Add( this.localName );
                this.namespaceURI = nameTable.Add( this.namespaceURI );
                this.atomized = true;
            }                
            if ( IsMatch( args.Node ) ) {
                this.changeCount++ ;
                this.curInd = -1;
                this.curElem = rootNode;
                if( args.Action == XmlNodeChangedAction.Insert )
                    this.empty = false;
            }
            this.matchCount = -1;
        }
 
        internal XmlElementList( XmlNode parent, string name ): this( parent ) {
            Debug.Assert( parent.Document != null );            
            XmlNameTable nt = parent.Document.NameTable;
            Debug.Assert( nt != null );
            asterisk = nt.Add("*");
            this.name = nt.Add( name );
            this.localName = null;
            this.namespaceURI = null;
        }
 
        internal XmlElementList( XmlNode parent, string localName, string namespaceURI ): this( parent ) {
            Debug.Assert( parent.Document != null );
            XmlNameTable nt = parent.Document.NameTable;
            Debug.Assert( nt != null );
            asterisk = nt.Add("*");
            this.localName = nt.Get( localName );
            this.namespaceURI = nt.Get( namespaceURI );
            if( (this.localName == null) || (this.namespaceURI== null) ) {
                this.empty = true;
                this.atomized = false;
                this.localName = localName;
                this.namespaceURI = namespaceURI;
            }   
                this.name = null;
        }
        
        internal int ChangeCount {
            get { return changeCount; }
        }
 
        // return the next element node that is in PreOrder
        private XmlNode NextElemInPreOrder( XmlNode curNode ) {
            Debug.Assert( curNode != null );
            //For preorder walking, first try its child
            XmlNode retNode = curNode.FirstChild;
            if ( retNode == null ) {
                //if no child, the next node forward will the be the NextSibling of the first ancestor which has NextSibling
                //so, first while-loop find out such an ancestor (until no more ancestor or the ancestor is the rootNode
                retNode = curNode;
                while ( retNode != null 
                        && retNode != rootNode 
                        && retNode.NextSibling == null ) {
                    retNode = retNode.ParentNode;
                }
                //then if such ancestor exists, set the retNode to its NextSibling
                if ( retNode != null && retNode != rootNode )
                    retNode = retNode.NextSibling;
            }
            if ( retNode == this.rootNode ) 
                //if reach the rootNode, consider having walked through the whole tree and no more element after the curNode
                retNode = null;
            return retNode;
        }
 
        // return the previous element node that is in PreOrder
        private XmlNode PrevElemInPreOrder( XmlNode curNode ) {
            Debug.Assert( curNode != null );
            //For preorder walking, the previous node will be the right-most node in the tree of PreviousSibling of the curNode
            XmlNode retNode = curNode.PreviousSibling;
            // so if the PreviousSibling is not null, going through the tree down to find the right-most node
            while ( retNode != null ) {
                if ( retNode.LastChild == null )
                    break;
                retNode = retNode.LastChild;
            }
            // if no PreviousSibling, the previous node will be the curNode's parentNode
            if ( retNode == null )
                retNode = curNode.ParentNode;
            // if the final retNode is rootNode, consider having walked through the tree and no more previous node
            if ( retNode == this.rootNode )
                retNode = null;
            return retNode;
        }
 
        // if the current node a matching element node
        private bool IsMatch ( XmlNode curNode ) {
            if (curNode.NodeType == XmlNodeType.Element) {
                if ( this.name != null ) {
                    if ( Ref.Equal(this.name, asterisk) || Ref.Equal(curNode.Name, this.name) )
                        return true;
                } 
                else {
                    if (
                        (Ref.Equal(this.localName, asterisk) || Ref.Equal(curNode.LocalName, this.localName) ) && 
                        (Ref.Equal(this.namespaceURI, asterisk) || curNode.NamespaceURI == this.namespaceURI )
                    ) {
                        return true;
                    }
                }
            }     
            return false;
        }
 
        private XmlNode GetMatchingNode( XmlNode n, bool bNext ) {
            Debug.Assert( n!= null );
            XmlNode node = n;            
            do {
                if ( bNext )
                    node = NextElemInPreOrder( node );
                else
                    node = PrevElemInPreOrder( node );
            } while ( node != null && !IsMatch( node ) );
            return node;
        }
 
        private XmlNode GetNthMatchingNode( XmlNode n, bool bNext, int nCount ) {
            Debug.Assert( n!= null );
            XmlNode node = n;
            for ( int ind = 0 ; ind < nCount; ind++ ) {
                node = GetMatchingNode( node, bNext );
                if ( node == null )
                    return null;
            } 
            return node;
        }
 
        //the function is for the enumerator to find out the next available matching element node
        public XmlNode GetNextNode( XmlNode n ) {
            if( this.empty == true )
                return null;
            XmlNode node = ( n == null ) ? rootNode : n;
            return GetMatchingNode( node, true );
        }
 
        public override XmlNode Item(int index) {
            if ( rootNode == null || index < 0 ) 
                return null;
 
            if( this.empty == true )
                return null;
            if ( curInd == index )
                return curElem;
            int nDiff = index - curInd;
            bool bForward = ( nDiff > 0 );
            if ( nDiff < 0 )
                nDiff = -nDiff;
            XmlNode node;
            if ( ( node = GetNthMatchingNode( curElem, bForward, nDiff ) ) != null ) {
                curInd = index;
                curElem = node;
                return curElem;
            } 
            return null;
        }
 
        public override int Count { 
            get { 
                if( this.empty == true )
                    return 0;
                if (this.matchCount < 0) {
                    int currMatchCount = 0;
                    int currChangeCount = this.changeCount;
                    XmlNode node = rootNode;
                    while ((node = GetMatchingNode(node, true)) != null) {
                        currMatchCount++;
                    }
                    if (currChangeCount != this.changeCount) {
                        return currMatchCount;
                    }
                    this.matchCount = currMatchCount;
                }
                return this.matchCount;
            }
        }
    
        public override IEnumerator GetEnumerator() {
            if( this.empty == true )
                return new XmlEmptyElementListEnumerator(this);;
            return new XmlElementListEnumerator(this);
        }
 
        protected override void PrivateDisposeNodeList() {
            GC.SuppressFinalize(this);
            Dispose(true);
        }
 
        protected virtual void Dispose(bool disposing) {
            if (this.listener != null) {
                XmlElementListListener listener = (XmlElementListListener)this.listener.Target;
                if (listener != null) {
                    listener.Unregister();
                }
                this.listener = null;
            }
        }
    }
 
     internal class XmlElementListEnumerator : IEnumerator {
        XmlElementList  list;
        XmlNode         curElem;
        int             changeCount; //save the total number that the dom tree has been changed ( insertion and deletetion ) when this enumerator is created
 
        public XmlElementListEnumerator( XmlElementList list ) {
            this.list = list;
            this.curElem = null;
            this.changeCount = list.ChangeCount;
        }
 
        public bool MoveNext() { 
            if ( list.ChangeCount != this.changeCount ) {
                //the number mismatch, there is new change(s) happened since last MoveNext() is called.
                throw new InvalidOperationException( Res.GetString(Res.Xdom_Enum_ElementList) );
            } 
            else  {
                curElem = list.GetNextNode( curElem );                
            }
            return curElem != null;
        }
 
        public void Reset() {
            curElem = null;
            //reset the number of changes to be synced with current dom tree as well
            this.changeCount = list.ChangeCount;
        }
 
        public object Current {
            get { return curElem; }
        }
    }
    
    internal class XmlEmptyElementListEnumerator : IEnumerator {        
        public XmlEmptyElementListEnumerator( XmlElementList list ) {
        }
 
        public bool MoveNext() { 
            return false;
        }
 
        public void Reset() {
        }
        
        public object Current {
            get { return null; }
        }
    }
 
    internal class XmlElementListListener {
        WeakReference elemList;
        XmlDocument doc;
        XmlNodeChangedEventHandler nodeChangeHandler = null;
 
        internal XmlElementListListener(XmlDocument doc, XmlElementList elemList) {
            this.doc = doc;
            this.elemList = new WeakReference(elemList);
            this.nodeChangeHandler = new XmlNodeChangedEventHandler( this.OnListChanged );
            doc.NodeInserted += this.nodeChangeHandler;
            doc.NodeRemoved += this.nodeChangeHandler;
        }
 
        private void OnListChanged( object sender, XmlNodeChangedEventArgs args ) {
            lock (this) {
                if (this.elemList != null) {
                    XmlElementList el = (XmlElementList)this.elemList.Target;
                    if (null != el) {
                        el.ConcurrencyCheck(args);
                    } else {
                        this.doc.NodeInserted -= this.nodeChangeHandler;
                        this.doc.NodeRemoved -= this.nodeChangeHandler;
                        this.elemList = null;
                    }
                }
            }
        }
 
        // This method is called from the finalizer of XmlElementList
        internal void Unregister() {
            lock (this) {
                if (elemList != null) {
                    this.doc.NodeInserted -= this.nodeChangeHandler;
                    this.doc.NodeRemoved -= this.nodeChangeHandler;
                    this.elemList = null;
                }
            }
        }
    }
}