ef190d83c1049d40f8597c730316126728cc9f96
[mono.git] / mcs / class / referencesource / System.Xml / System / Xml / Dom / XmlElementList.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="XmlElementList.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 //------------------------------------------------------------------------------
7
8 namespace System.Xml {
9     using System;
10     using System.Collections;
11     using System.Diagnostics;
12
13     internal class XmlElementList: XmlNodeList {
14         string      asterisk;
15         int         changeCount; //recording the total number that the dom tree has been changed ( insertion and deletetion )
16         //the member vars below are saved for further reconstruction        
17         string      name;         //only one of 2 string groups will be initialized depends on which constructor is called.
18         string      localName; 
19         string      namespaceURI;
20         XmlNode     rootNode;
21         // the memeber vars belwo serves the optimization of accessing of the elements in the list
22         int         curInd;       // -1 means the starting point for a new search round
23         XmlNode     curElem;      // if sets to rootNode, means the starting point for a new search round
24         bool        empty;        // whether the list is empty
25         bool        atomized;     //whether the localname and namespaceuri are aomized
26         int         matchCount;   // cached list count. -1 means it needs reconstruction
27
28         WeakReference listener;   // XmlElementListListener
29
30         private XmlElementList( XmlNode parent) {
31             Debug.Assert ( parent != null );
32             Debug.Assert( parent.NodeType == XmlNodeType.Element || parent.NodeType == XmlNodeType.Document );
33             this.rootNode = parent;
34             Debug.Assert( parent.Document != null );
35             this.curInd = -1;
36             this.curElem = rootNode;
37             this.changeCount = 0;
38             this.empty = false;
39             this.atomized = true; 
40             this.matchCount = -1;
41             // This can be a regular reference, but it would cause some kind of loop inside the GC
42             this.listener = new WeakReference(new XmlElementListListener(parent.Document, this));
43         }
44
45         ~XmlElementList() {
46             Dispose(false);
47         }
48
49         internal void ConcurrencyCheck(XmlNodeChangedEventArgs args){
50             if( atomized == false ) {
51                 XmlNameTable nameTable = this.rootNode.Document.NameTable;
52                 this.localName = nameTable.Add( this.localName );
53                 this.namespaceURI = nameTable.Add( this.namespaceURI );
54                 this.atomized = true;
55             }                
56             if ( IsMatch( args.Node ) ) {
57                 this.changeCount++ ;
58                 this.curInd = -1;
59                 this.curElem = rootNode;
60                 if( args.Action == XmlNodeChangedAction.Insert )
61                     this.empty = false;
62             }
63             this.matchCount = -1;
64         }
65
66         internal XmlElementList( XmlNode parent, string name ): this( parent ) {
67             Debug.Assert( parent.Document != null );            
68             XmlNameTable nt = parent.Document.NameTable;
69             Debug.Assert( nt != null );
70             asterisk = nt.Add("*");
71             this.name = nt.Add( name );
72             this.localName = null;
73             this.namespaceURI = null;
74         }
75
76         internal XmlElementList( XmlNode parent, string localName, string namespaceURI ): this( parent ) {
77             Debug.Assert( parent.Document != null );
78             XmlNameTable nt = parent.Document.NameTable;
79             Debug.Assert( nt != null );
80             asterisk = nt.Add("*");
81             this.localName = nt.Get( localName );
82             this.namespaceURI = nt.Get( namespaceURI );
83             if( (this.localName == null) || (this.namespaceURI== null) ) {
84                 this.empty = true;
85                 this.atomized = false;
86                 this.localName = localName;
87                 this.namespaceURI = namespaceURI;
88             }   
89                 this.name = null;
90         }
91         
92         internal int ChangeCount {
93             get { return changeCount; }
94         }
95
96         // return the next element node that is in PreOrder
97         private XmlNode NextElemInPreOrder( XmlNode curNode ) {
98             Debug.Assert( curNode != null );
99             //For preorder walking, first try its child
100             XmlNode retNode = curNode.FirstChild;
101             if ( retNode == null ) {
102                 //if no child, the next node forward will the be the NextSibling of the first ancestor which has NextSibling
103                 //so, first while-loop find out such an ancestor (until no more ancestor or the ancestor is the rootNode
104                 retNode = curNode;
105                 while ( retNode != null 
106                         && retNode != rootNode 
107                         && retNode.NextSibling == null ) {
108                     retNode = retNode.ParentNode;
109                 }
110                 //then if such ancestor exists, set the retNode to its NextSibling
111                 if ( retNode != null && retNode != rootNode )
112                     retNode = retNode.NextSibling;
113             }
114             if ( retNode == this.rootNode ) 
115                 //if reach the rootNode, consider having walked through the whole tree and no more element after the curNode
116                 retNode = null;
117             return retNode;
118         }
119
120         // return the previous element node that is in PreOrder
121         private XmlNode PrevElemInPreOrder( XmlNode curNode ) {
122             Debug.Assert( curNode != null );
123             //For preorder walking, the previous node will be the right-most node in the tree of PreviousSibling of the curNode
124             XmlNode retNode = curNode.PreviousSibling;
125             // so if the PreviousSibling is not null, going through the tree down to find the right-most node
126             while ( retNode != null ) {
127                 if ( retNode.LastChild == null )
128                     break;
129                 retNode = retNode.LastChild;
130             }
131             // if no PreviousSibling, the previous node will be the curNode's parentNode
132             if ( retNode == null )
133                 retNode = curNode.ParentNode;
134             // if the final retNode is rootNode, consider having walked through the tree and no more previous node
135             if ( retNode == this.rootNode )
136                 retNode = null;
137             return retNode;
138         }
139
140         // if the current node a matching element node
141         private bool IsMatch ( XmlNode curNode ) {
142             if (curNode.NodeType == XmlNodeType.Element) {
143                 if ( this.name != null ) {
144                     if ( Ref.Equal(this.name, asterisk) || Ref.Equal(curNode.Name, this.name) )
145                         return true;
146                 } 
147                 else {
148                     if (
149                         (Ref.Equal(this.localName, asterisk) || Ref.Equal(curNode.LocalName, this.localName) ) && 
150                         (Ref.Equal(this.namespaceURI, asterisk) || curNode.NamespaceURI == this.namespaceURI )
151                     ) {
152                         return true;
153                     }
154                 }
155             }     
156             return false;
157         }
158
159         private XmlNode GetMatchingNode( XmlNode n, bool bNext ) {
160             Debug.Assert( n!= null );
161             XmlNode node = n;            
162             do {
163                 if ( bNext )
164                     node = NextElemInPreOrder( node );
165                 else
166                     node = PrevElemInPreOrder( node );
167             } while ( node != null && !IsMatch( node ) );
168             return node;
169         }
170
171         private XmlNode GetNthMatchingNode( XmlNode n, bool bNext, int nCount ) {
172             Debug.Assert( n!= null );
173             XmlNode node = n;
174             for ( int ind = 0 ; ind < nCount; ind++ ) {
175                 node = GetMatchingNode( node, bNext );
176                 if ( node == null )
177                     return null;
178             } 
179             return node;
180         }
181
182         //the function is for the enumerator to find out the next available matching element node
183         public XmlNode GetNextNode( XmlNode n ) {
184             if( this.empty == true )
185                 return null;
186             XmlNode node = ( n == null ) ? rootNode : n;
187             return GetMatchingNode( node, true );
188         }
189
190         public override XmlNode Item(int index) {
191             if ( rootNode == null || index < 0 ) 
192                 return null;
193
194             if( this.empty == true )
195                 return null;
196             if ( curInd == index )
197                 return curElem;
198             int nDiff = index - curInd;
199             bool bForward = ( nDiff > 0 );
200             if ( nDiff < 0 )
201                 nDiff = -nDiff;
202             XmlNode node;
203             if ( ( node = GetNthMatchingNode( curElem, bForward, nDiff ) ) != null ) {
204                 curInd = index;
205                 curElem = node;
206                 return curElem;
207             } 
208             return null;
209         }
210
211         public override int Count { 
212             get { 
213                 if( this.empty == true )
214                     return 0;
215                 if (this.matchCount < 0) {
216                     int currMatchCount = 0;
217                     int currChangeCount = this.changeCount;
218                     XmlNode node = rootNode;
219                     while ((node = GetMatchingNode(node, true)) != null) {
220                         currMatchCount++;
221                     }
222                     if (currChangeCount != this.changeCount) {
223                         return currMatchCount;
224                     }
225                     this.matchCount = currMatchCount;
226                 }
227                 return this.matchCount;
228             }
229         }
230     
231         public override IEnumerator GetEnumerator() {
232             if( this.empty == true )
233                 return new XmlEmptyElementListEnumerator(this);;
234             return new XmlElementListEnumerator(this);
235         }
236
237         protected override void PrivateDisposeNodeList() {
238             GC.SuppressFinalize(this);
239             Dispose(true);
240         }
241
242         protected virtual void Dispose(bool disposing) {
243             if (this.listener != null) {
244                 XmlElementListListener listener = (XmlElementListListener)this.listener.Target;
245                 if (listener != null) {
246                     listener.Unregister();
247                 }
248                 this.listener = null;
249             }
250         }
251     }
252
253      internal class XmlElementListEnumerator : IEnumerator {
254         XmlElementList  list;
255         XmlNode         curElem;
256         int             changeCount; //save the total number that the dom tree has been changed ( insertion and deletetion ) when this enumerator is created
257
258         public XmlElementListEnumerator( XmlElementList list ) {
259             this.list = list;
260             this.curElem = null;
261             this.changeCount = list.ChangeCount;
262         }
263
264         public bool MoveNext() { 
265             if ( list.ChangeCount != this.changeCount ) {
266                 //the number mismatch, there is new change(s) happened since last MoveNext() is called.
267                 throw new InvalidOperationException( Res.GetString(Res.Xdom_Enum_ElementList) );
268             } 
269             else  {
270                 curElem = list.GetNextNode( curElem );                
271             }
272             return curElem != null;
273         }
274
275         public void Reset() {
276             curElem = null;
277             //reset the number of changes to be synced with current dom tree as well
278             this.changeCount = list.ChangeCount;
279         }
280
281         public object Current {
282             get { return curElem; }
283         }
284     }
285     
286     internal class XmlEmptyElementListEnumerator : IEnumerator {        
287         public XmlEmptyElementListEnumerator( XmlElementList list ) {
288         }
289
290         public bool MoveNext() { 
291             return false;
292         }
293
294         public void Reset() {
295         }
296         
297         public object Current {
298             get { return null; }
299         }
300     }
301
302     internal class XmlElementListListener {
303         WeakReference elemList;
304         XmlDocument doc;
305         XmlNodeChangedEventHandler nodeChangeHandler = null;
306
307         internal XmlElementListListener(XmlDocument doc, XmlElementList elemList) {
308             this.doc = doc;
309             this.elemList = new WeakReference(elemList);
310             this.nodeChangeHandler = new XmlNodeChangedEventHandler( this.OnListChanged );
311             doc.NodeInserted += this.nodeChangeHandler;
312             doc.NodeRemoved += this.nodeChangeHandler;
313         }
314
315         private void OnListChanged( object sender, XmlNodeChangedEventArgs args ) {
316             lock (this) {
317                 if (this.elemList != null) {
318                     XmlElementList el = (XmlElementList)this.elemList.Target;
319                     if (null != el) {
320                         el.ConcurrencyCheck(args);
321                     } else {
322                         this.doc.NodeInserted -= this.nodeChangeHandler;
323                         this.doc.NodeRemoved -= this.nodeChangeHandler;
324                         this.elemList = null;
325                     }
326                 }
327             }
328         }
329
330         // This method is called from the finalizer of XmlElementList
331         internal void Unregister() {
332             lock (this) {
333                 if (elemList != null) {
334                     this.doc.NodeInserted -= this.nodeChangeHandler;
335                     this.doc.NodeRemoved -= this.nodeChangeHandler;
336                     this.elemList = null;
337                 }
338             }
339         }
340     }
341 }