Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Document/TextSegmentCollection.cs @ 12850

Last change on this file since 12850 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 30.2 KB
Line 
1// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4// software and associated documentation files (the "Software"), to deal in the Software
5// without restriction, including without limitation the rights to use, copy, modify, merge,
6// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7// to whom the Software is furnished to do so, subject to the following conditions:
8//
9// The above copyright notice and this permission notice shall be included in all copies or
10// substantial portions of the Software.
11//
12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17// DEALINGS IN THE SOFTWARE.
18
19using System;
20using System.Collections.Generic;
21using System.Collections.ObjectModel;
22using System.Diagnostics;
23using System.Linq;
24using System.Text;
25using System.Windows;
26using ICSharpCode.AvalonEdit.Utils;
27#if NREFACTORY
28using ICSharpCode.NRefactory.Editor;
29#endif
30
31namespace ICSharpCode.AvalonEdit.Document
32{
33  /// <summary>
34  /// Interface to allow TextSegments to access the TextSegmentCollection - we cannot use a direct reference
35  /// because TextSegmentCollection is generic.
36  /// </summary>
37  interface ISegmentTree
38  {
39    void Add(TextSegment s);
40    void Remove(TextSegment s);
41    void UpdateAugmentedData(TextSegment s);
42  }
43 
44  /// <summary>
45  /// <para>
46  /// A collection of text segments that supports efficient lookup of segments
47  /// intersecting with another segment.
48  /// </para>
49  /// </summary>
50  /// <remarks><inheritdoc cref="TextSegment"/></remarks>
51  /// <see cref="TextSegment"/>
52  public sealed class TextSegmentCollection<T> : ICollection<T>, ISegmentTree, IWeakEventListener where T : TextSegment
53  {
54    // Implementation: this is basically a mixture of an augmented interval tree
55    // and the TextAnchorTree.
56   
57    // WARNING: you need to understand interval trees (the version with the augmented 'high'/'max' field)
58    // and how the TextAnchorTree works before you have any chance of understanding this code.
59   
60    // This means that every node holds two "segments":
61    // one like the segments in the text anchor tree to support efficient offset changes
62    // and another that is the interval as seen by the user
63   
64    // So basically, the tree contains a list of contiguous node segments of the first kind,
65    // with interval segments starting at the end of every node segment.
66   
67    // Performance:
68    // Add is O(lg n)
69    // Remove is O(lg n)
70    // DocumentChanged is O(m * lg n), with m the number of segments that intersect with the changed document section
71    // FindFirstSegmentWithStartAfter is O(m + lg n) with m being the number of segments at the same offset as the result segment
72    // FindIntersectingSegments is O(m + lg n) with m being the number of intersecting segments.
73   
74    int count;
75    TextSegment root;
76    bool isConnectedToDocument;
77   
78    #region Constructor
79    /// <summary>
80    /// Creates a new TextSegmentCollection that needs manual calls to <see cref="UpdateOffsets(DocumentChangeEventArgs)"/>.
81    /// </summary>
82    public TextSegmentCollection()
83    {
84    }
85   
86    /// <summary>
87    /// Creates a new TextSegmentCollection that updates the offsets automatically.
88    /// </summary>
89    /// <param name="textDocument">The document to which the text segments
90    /// that will be added to the tree belong. When the document changes, the
91    /// position of the text segments will be updated accordingly.</param>
92    public TextSegmentCollection(TextDocument textDocument)
93    {
94      if (textDocument == null)
95        throw new ArgumentNullException("textDocument");
96     
97      textDocument.VerifyAccess();
98      isConnectedToDocument = true;
99      TextDocumentWeakEventManager.Changed.AddListener(textDocument, this);
100    }
101    #endregion
102   
103    #region OnDocumentChanged / UpdateOffsets
104    bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
105    {
106      if (managerType == typeof(TextDocumentWeakEventManager.Changed)) {
107        OnDocumentChanged((DocumentChangeEventArgs)e);
108        return true;
109      }
110      return false;
111    }
112   
113    /// <summary>
114    /// Updates the start and end offsets of all segments stored in this collection.
115    /// </summary>
116    /// <param name="e">DocumentChangeEventArgs instance describing the change to the document.</param>
117    public void UpdateOffsets(DocumentChangeEventArgs e)
118    {
119      if (e == null)
120        throw new ArgumentNullException("e");
121      if (isConnectedToDocument)
122        throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
123      OnDocumentChanged(e);
124      CheckProperties();
125    }
126   
127    void OnDocumentChanged(DocumentChangeEventArgs e)
128    {
129      OffsetChangeMap map = e.OffsetChangeMapOrNull;
130      if (map != null) {
131        foreach (OffsetChangeMapEntry entry in map) {
132          UpdateOffsetsInternal(entry);
133        }
134      } else {
135        UpdateOffsetsInternal(e.CreateSingleChangeMapEntry());
136      }
137    }
138   
139    /// <summary>
140    /// Updates the start and end offsets of all segments stored in this collection.
141    /// </summary>
142    /// <param name="change">OffsetChangeMapEntry instance describing the change to the document.</param>
143    public void UpdateOffsets(OffsetChangeMapEntry change)
144    {
145      if (isConnectedToDocument)
146        throw new InvalidOperationException("This TextSegmentCollection will automatically update offsets; do not call UpdateOffsets manually!");
147      UpdateOffsetsInternal(change);
148      CheckProperties();
149    }
150    #endregion
151   
152    #region UpdateOffsets (implementation)
153    void UpdateOffsetsInternal(OffsetChangeMapEntry change)
154    {
155      // Special case pure insertions, because they don't always cause a text segment to increase in size when the replaced region
156      // is inside a segment (when offset is at start or end of a text semgent).
157      if (change.RemovalLength == 0) {
158        InsertText(change.Offset, change.InsertionLength);
159      } else {
160        ReplaceText(change);
161      }
162    }
163   
164    void InsertText(int offset, int length)
165    {
166      if (length == 0)
167        return;
168     
169      // enlarge segments that contain offset (excluding those that have offset as endpoint)
170      foreach (TextSegment segment in FindSegmentsContaining(offset)) {
171        if (segment.StartOffset < offset && offset < segment.EndOffset) {
172          segment.Length += length;
173        }
174      }
175     
176      // move start offsets of all segments >= offset
177      TextSegment node = FindFirstSegmentWithStartAfter(offset);
178      if (node != null) {
179        node.nodeLength += length;
180        UpdateAugmentedData(node);
181      }
182    }
183   
184    void ReplaceText(OffsetChangeMapEntry change)
185    {
186      Debug.Assert(change.RemovalLength > 0);
187      int offset = change.Offset;
188      foreach (TextSegment segment in FindOverlappingSegments(offset, change.RemovalLength)) {
189        if (segment.StartOffset <= offset) {
190          if (segment.EndOffset >= offset + change.RemovalLength) {
191            // Replacement inside segment: adjust segment length
192            segment.Length += change.InsertionLength - change.RemovalLength;
193          } else {
194            // Replacement starting inside segment and ending after segment end: set segment end to removal position
195            //segment.EndOffset = offset;
196            segment.Length = offset - segment.StartOffset;
197          }
198        } else {
199          // Replacement starting in front of text segment and running into segment.
200          // Keep segment.EndOffset constant and move segment.StartOffset to the end of the replacement
201          int remainingLength = segment.EndOffset - (offset + change.RemovalLength);
202          RemoveSegment(segment);
203          segment.StartOffset = offset + change.RemovalLength;
204          segment.Length = Math.Max(0, remainingLength);
205          AddSegment(segment);
206        }
207      }
208      // move start offsets of all segments > offset
209      TextSegment node = FindFirstSegmentWithStartAfter(offset + 1);
210      if (node != null) {
211        Debug.Assert(node.nodeLength >= change.RemovalLength);
212        node.nodeLength += change.InsertionLength - change.RemovalLength;
213        UpdateAugmentedData(node);
214      }
215    }
216    #endregion
217   
218    #region Add
219    /// <summary>
220    /// Adds the specified segment to the tree. This will cause the segment to update when the
221    /// document changes.
222    /// </summary>
223    public void Add(T item)
224    {
225      if (item == null)
226        throw new ArgumentNullException("item");
227      if (item.ownerTree != null)
228        throw new ArgumentException("The segment is already added to a SegmentCollection.");
229      AddSegment(item);
230    }
231   
232    void ISegmentTree.Add(TextSegment s)
233    {
234      AddSegment(s);
235    }
236   
237    void AddSegment(TextSegment node)
238    {
239      int insertionOffset = node.StartOffset;
240      node.distanceToMaxEnd = node.segmentLength;
241      if (root == null) {
242        root = node;
243        node.totalNodeLength = node.nodeLength;
244      } else if (insertionOffset >= root.totalNodeLength) {
245        // append segment at end of tree
246        node.nodeLength = node.totalNodeLength = insertionOffset - root.totalNodeLength;
247        InsertAsRight(root.RightMost, node);
248      } else {
249        // insert in middle of tree
250        TextSegment n = FindNode(ref insertionOffset);
251        Debug.Assert(insertionOffset < n.nodeLength);
252        // split node segment 'n' at offset
253        node.totalNodeLength = node.nodeLength = insertionOffset;
254        n.nodeLength -= insertionOffset;
255        InsertBefore(n, node);
256      }
257      node.ownerTree = this;
258      count++;
259      CheckProperties();
260    }
261   
262    void InsertBefore(TextSegment node, TextSegment newNode)
263    {
264      if (node.left == null) {
265        InsertAsLeft(node, newNode);
266      } else {
267        InsertAsRight(node.left.RightMost, newNode);
268      }
269    }
270    #endregion
271   
272    #region GetNextSegment / GetPreviousSegment
273    /// <summary>
274    /// Gets the next segment after the specified segment.
275    /// Segments are sorted by their start offset.
276    /// Returns null if segment is the last segment.
277    /// </summary>
278    public T GetNextSegment(T segment)
279    {
280      if (!Contains(segment))
281        throw new ArgumentException("segment is not inside the segment tree");
282      return (T)segment.Successor;
283    }
284   
285    /// <summary>
286    /// Gets the previous segment before the specified segment.
287    /// Segments are sorted by their start offset.
288    /// Returns null if segment is the first segment.
289    /// </summary>
290    public T GetPreviousSegment(T segment)
291    {
292      if (!Contains(segment))
293        throw new ArgumentException("segment is not inside the segment tree");
294      return (T)segment.Predecessor;
295    }
296    #endregion
297   
298    #region FirstSegment/LastSegment
299    /// <summary>
300    /// Returns the first segment in the collection or null, if the collection is empty.
301    /// </summary>
302    public T FirstSegment {
303      get {
304        return root == null ? null : (T)root.LeftMost;
305      }
306    }
307   
308    /// <summary>
309    /// Returns the last segment in the collection or null, if the collection is empty.
310    /// </summary>
311    public T LastSegment {
312      get {
313        return root == null ? null : (T)root.RightMost;
314      }
315    }
316    #endregion
317   
318    #region FindFirstSegmentWithStartAfter
319    /// <summary>
320    /// Gets the first segment with a start offset greater or equal to <paramref name="startOffset"/>.
321    /// Returns null if no such segment is found.
322    /// </summary>
323    public T FindFirstSegmentWithStartAfter(int startOffset)
324    {
325      if (root == null)
326        return null;
327      if (startOffset <= 0)
328        return (T)root.LeftMost;
329      TextSegment s = FindNode(ref startOffset);
330      // startOffset means that the previous segment is starting at the offset we were looking for
331      while (startOffset == 0) {
332        TextSegment p = (s == null) ? root.RightMost : s.Predecessor;
333        // There must always be a predecessor: if we were looking for the first node, we would have already
334        // returned it as root.LeftMost above.
335        Debug.Assert(p != null);
336        startOffset += p.nodeLength;
337        s = p;
338      }
339      return (T)s;
340    }
341   
342    /// <summary>
343    /// Finds the node at the specified offset.
344    /// After the method has run, offset is relative to the beginning of the returned node.
345    /// </summary>
346    TextSegment FindNode(ref int offset)
347    {
348      TextSegment n = root;
349      while (true) {
350        if (n.left != null) {
351          if (offset < n.left.totalNodeLength) {
352            n = n.left; // descend into left subtree
353            continue;
354          } else {
355            offset -= n.left.totalNodeLength; // skip left subtree
356          }
357        }
358        if (offset < n.nodeLength) {
359          return n; // found correct node
360        } else {
361          offset -= n.nodeLength; // skip this node
362        }
363        if (n.right != null) {
364          n = n.right; // descend into right subtree
365        } else {
366          // didn't find any node containing the offset
367          return null;
368        }
369      }
370    }
371    #endregion
372   
373    #region FindOverlappingSegments
374    /// <summary>
375    /// Finds all segments that contain the given offset.
376    /// (StartOffset &lt;= offset &lt;= EndOffset)
377    /// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
378    /// </summary>
379    /// <returns>Returns a new collection containing the results of the query.
380    /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
381    public ReadOnlyCollection<T> FindSegmentsContaining(int offset)
382    {
383      return FindOverlappingSegments(offset, 0);
384    }
385   
386    /// <summary>
387    /// Finds all segments that overlap with the given segment (including touching segments).
388    /// </summary>
389    /// <returns>Returns a new collection containing the results of the query.
390    /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
391    public ReadOnlyCollection<T> FindOverlappingSegments(ISegment segment)
392    {
393      if (segment == null)
394        throw new ArgumentNullException("segment");
395      return FindOverlappingSegments(segment.Offset, segment.Length);
396    }
397   
398    /// <summary>
399    /// Finds all segments that overlap with the given segment (including touching segments).
400    /// Segments are returned in the order given by GetNextSegment/GetPreviousSegment.
401    /// </summary>
402    /// <returns>Returns a new collection containing the results of the query.
403    /// This means it is safe to modify the TextSegmentCollection while iterating through the result collection.</returns>
404    public ReadOnlyCollection<T> FindOverlappingSegments(int offset, int length)
405    {
406      ThrowUtil.CheckNotNegative(length, "length");
407      List<T> results = new List<T>();
408      if (root != null) {
409        FindOverlappingSegments(results, root, offset, offset + length);
410      }
411      return results.AsReadOnly();
412    }
413   
414    void FindOverlappingSegments(List<T> results, TextSegment node, int low, int high)
415    {
416      // low and high are relative to node.LeftMost startpos (not node.LeftMost.Offset)
417      if (high < 0) {
418        // node is irrelevant for search because all intervals in node are after high
419        return;
420      }
421     
422      // find values relative to node.Offset
423      int nodeLow = low - node.nodeLength;
424      int nodeHigh = high - node.nodeLength;
425      if (node.left != null) {
426        nodeLow -= node.left.totalNodeLength;
427        nodeHigh -= node.left.totalNodeLength;
428      }
429     
430      if (node.distanceToMaxEnd < nodeLow) {
431        // node is irrelevant for search because all intervals in node are before low
432        return;
433      }
434     
435      if (node.left != null)
436        FindOverlappingSegments(results, node.left, low, high);
437     
438      if (nodeHigh < 0) {
439        // node and everything in node.right is before low
440        return;
441      }
442     
443      if (nodeLow <= node.segmentLength) {
444        results.Add((T)node);
445      }
446     
447      if (node.right != null)
448        FindOverlappingSegments(results, node.right, nodeLow, nodeHigh);
449    }
450    #endregion
451   
452    #region UpdateAugmentedData
453    void UpdateAugmentedData(TextSegment node)
454    {
455      int totalLength = node.nodeLength;
456      int distanceToMaxEnd = node.segmentLength;
457      if (node.left != null) {
458        totalLength += node.left.totalNodeLength;
459       
460        int leftDTME = node.left.distanceToMaxEnd;
461        // dtme is relative, so convert it to the coordinates of node:
462        if (node.left.right != null)
463          leftDTME -= node.left.right.totalNodeLength;
464        leftDTME -= node.nodeLength;
465        if (leftDTME > distanceToMaxEnd)
466          distanceToMaxEnd = leftDTME;
467      }
468      if (node.right != null) {
469        totalLength += node.right.totalNodeLength;
470       
471        int rightDTME = node.right.distanceToMaxEnd;
472        // dtme is relative, so convert it to the coordinates of node:
473        rightDTME += node.right.nodeLength;
474        if (node.right.left != null)
475          rightDTME += node.right.left.totalNodeLength;
476        if (rightDTME > distanceToMaxEnd)
477          distanceToMaxEnd = rightDTME;
478      }
479      if (node.totalNodeLength != totalLength
480          || node.distanceToMaxEnd != distanceToMaxEnd)
481      {
482        node.totalNodeLength = totalLength;
483        node.distanceToMaxEnd = distanceToMaxEnd;
484        if (node.parent != null)
485          UpdateAugmentedData(node.parent);
486      }
487    }
488   
489    void ISegmentTree.UpdateAugmentedData(TextSegment node)
490    {
491      UpdateAugmentedData(node);
492    }
493    #endregion
494   
495    #region Remove
496    /// <summary>
497    /// Removes the specified segment from the tree. This will cause the segment to not update
498    /// anymore when the document changes.
499    /// </summary>
500    public bool Remove(T item)
501    {
502      if (!Contains(item))
503        return false;
504      RemoveSegment(item);
505      return true;
506    }
507   
508    void ISegmentTree.Remove(TextSegment s)
509    {
510      RemoveSegment(s);
511    }
512   
513    void RemoveSegment(TextSegment s)
514    {
515      int oldOffset = s.StartOffset;
516      TextSegment successor = s.Successor;
517      if (successor != null)
518        successor.nodeLength += s.nodeLength;
519      RemoveNode(s);
520      if (successor != null)
521        UpdateAugmentedData(successor);
522      Disconnect(s, oldOffset);
523      CheckProperties();
524    }
525   
526    void Disconnect(TextSegment s, int offset)
527    {
528      s.left = s.right = s.parent = null;
529      s.ownerTree = null;
530      s.nodeLength = offset;
531      count--;
532    }
533   
534    /// <summary>
535    /// Removes all segments from the tree.
536    /// </summary>
537    public void Clear()
538    {
539      T[] segments = this.ToArray();
540      root = null;
541      int offset = 0;
542      foreach (TextSegment s in segments) {
543        offset += s.nodeLength;
544        Disconnect(s, offset);
545      }
546      CheckProperties();
547    }
548    #endregion
549   
550    #region CheckProperties
551    [Conditional("DATACONSISTENCYTEST")]
552    internal void CheckProperties()
553    {
554      #if DEBUG
555      if (root != null) {
556        CheckProperties(root);
557       
558        // check red-black property:
559        int blackCount = -1;
560        CheckNodeProperties(root, null, RED, 0, ref blackCount);
561      }
562     
563      int expectedCount = 0;
564      // we cannot trust LINQ not to call ICollection.Count, so we need this loop
565      // to count the elements in the tree
566      using (IEnumerator<T> en = GetEnumerator()) {
567        while (en.MoveNext()) expectedCount++;
568      }
569      Debug.Assert(count == expectedCount);
570      #endif
571    }
572   
573    #if DEBUG
574    void CheckProperties(TextSegment node)
575    {
576      int totalLength = node.nodeLength;
577      int distanceToMaxEnd = node.segmentLength;
578      if (node.left != null) {
579        CheckProperties(node.left);
580        totalLength += node.left.totalNodeLength;
581        distanceToMaxEnd = Math.Max(distanceToMaxEnd,
582                                    node.left.distanceToMaxEnd + node.left.StartOffset - node.StartOffset);
583      }
584      if (node.right != null) {
585        CheckProperties(node.right);
586        totalLength += node.right.totalNodeLength;
587        distanceToMaxEnd = Math.Max(distanceToMaxEnd,
588                                    node.right.distanceToMaxEnd + node.right.StartOffset - node.StartOffset);
589      }
590      Debug.Assert(node.totalNodeLength == totalLength);
591      Debug.Assert(node.distanceToMaxEnd == distanceToMaxEnd);
592    }
593   
594    /*
595    1. A node is either red or black.
596    2. The root is black.
597    3. All leaves are black. (The leaves are the NIL children.)
598    4. Both children of every red node are black. (So every red node must have a black parent.)
599    5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
600     */
601    void CheckNodeProperties(TextSegment node, TextSegment parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
602    {
603      if (node == null) return;
604     
605      Debug.Assert(node.parent == parentNode);
606     
607      if (parentColor == RED) {
608        Debug.Assert(node.color == BLACK);
609      }
610      if (node.color == BLACK) {
611        blackCount++;
612      }
613      if (node.left == null && node.right == null) {
614        // node is a leaf node:
615        if (expectedBlackCount == -1)
616          expectedBlackCount = blackCount;
617        else
618          Debug.Assert(expectedBlackCount == blackCount);
619      }
620      CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
621      CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
622    }
623   
624    static void AppendTreeToString(TextSegment node, StringBuilder b, int indent)
625    {
626      if (node.color == RED)
627        b.Append("RED   ");
628      else
629        b.Append("BLACK ");
630      b.AppendLine(node.ToString() + node.ToDebugString());
631      indent += 2;
632      if (node.left != null) {
633        b.Append(' ', indent);
634        b.Append("L: ");
635        AppendTreeToString(node.left, b, indent);
636      }
637      if (node.right != null) {
638        b.Append(' ', indent);
639        b.Append("R: ");
640        AppendTreeToString(node.right, b, indent);
641      }
642    }
643    #endif
644   
645    internal string GetTreeAsString()
646    {
647      #if DEBUG
648      StringBuilder b = new StringBuilder();
649      if (root != null)
650        AppendTreeToString(root, b, 0);
651      return b.ToString();
652      #else
653      return "Not available in release build.";
654      #endif
655    }
656    #endregion
657   
658    #region Red/Black Tree
659    internal const bool RED = true;
660    internal const bool BLACK = false;
661   
662    void InsertAsLeft(TextSegment parentNode, TextSegment newNode)
663    {
664      Debug.Assert(parentNode.left == null);
665      parentNode.left = newNode;
666      newNode.parent = parentNode;
667      newNode.color = RED;
668      UpdateAugmentedData(parentNode);
669      FixTreeOnInsert(newNode);
670    }
671   
672    void InsertAsRight(TextSegment parentNode, TextSegment newNode)
673    {
674      Debug.Assert(parentNode.right == null);
675      parentNode.right = newNode;
676      newNode.parent = parentNode;
677      newNode.color = RED;
678      UpdateAugmentedData(parentNode);
679      FixTreeOnInsert(newNode);
680    }
681   
682    void FixTreeOnInsert(TextSegment node)
683    {
684      Debug.Assert(node != null);
685      Debug.Assert(node.color == RED);
686      Debug.Assert(node.left == null || node.left.color == BLACK);
687      Debug.Assert(node.right == null || node.right.color == BLACK);
688     
689      TextSegment parentNode = node.parent;
690      if (parentNode == null) {
691        // we inserted in the root -> the node must be black
692        // since this is a root node, making the node black increments the number of black nodes
693        // on all paths by one, so it is still the same for all paths.
694        node.color = BLACK;
695        return;
696      }
697      if (parentNode.color == BLACK) {
698        // if the parent node where we inserted was black, our red node is placed correctly.
699        // since we inserted a red node, the number of black nodes on each path is unchanged
700        // -> the tree is still balanced
701        return;
702      }
703      // parentNode is red, so there is a conflict here!
704     
705      // because the root is black, parentNode is not the root -> there is a grandparent node
706      TextSegment grandparentNode = parentNode.parent;
707      TextSegment uncleNode = Sibling(parentNode);
708      if (uncleNode != null && uncleNode.color == RED) {
709        parentNode.color = BLACK;
710        uncleNode.color = BLACK;
711        grandparentNode.color = RED;
712        FixTreeOnInsert(grandparentNode);
713        return;
714      }
715      // now we know: parent is red but uncle is black
716      // First rotation:
717      if (node == parentNode.right && parentNode == grandparentNode.left) {
718        RotateLeft(parentNode);
719        node = node.left;
720      } else if (node == parentNode.left && parentNode == grandparentNode.right) {
721        RotateRight(parentNode);
722        node = node.right;
723      }
724      // because node might have changed, reassign variables:
725      parentNode = node.parent;
726      grandparentNode = parentNode.parent;
727     
728      // Now recolor a bit:
729      parentNode.color = BLACK;
730      grandparentNode.color = RED;
731      // Second rotation:
732      if (node == parentNode.left && parentNode == grandparentNode.left) {
733        RotateRight(grandparentNode);
734      } else {
735        // because of the first rotation, this is guaranteed:
736        Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
737        RotateLeft(grandparentNode);
738      }
739    }
740   
741    void RemoveNode(TextSegment removedNode)
742    {
743      if (removedNode.left != null && removedNode.right != null) {
744        // replace removedNode with it's in-order successor
745       
746        TextSegment leftMost = removedNode.right.LeftMost;
747        RemoveNode(leftMost); // remove leftMost from its current location
748       
749        // and overwrite the removedNode with it
750        ReplaceNode(removedNode, leftMost);
751        leftMost.left = removedNode.left;
752        if (leftMost.left != null) leftMost.left.parent = leftMost;
753        leftMost.right = removedNode.right;
754        if (leftMost.right != null) leftMost.right.parent = leftMost;
755        leftMost.color = removedNode.color;
756       
757        UpdateAugmentedData(leftMost);
758        if (leftMost.parent != null) UpdateAugmentedData(leftMost.parent);
759        return;
760      }
761     
762      // now either removedNode.left or removedNode.right is null
763      // get the remaining child
764      TextSegment parentNode = removedNode.parent;
765      TextSegment childNode = removedNode.left ?? removedNode.right;
766      ReplaceNode(removedNode, childNode);
767      if (parentNode != null) UpdateAugmentedData(parentNode);
768      if (removedNode.color == BLACK) {
769        if (childNode != null && childNode.color == RED) {
770          childNode.color = BLACK;
771        } else {
772          FixTreeOnDelete(childNode, parentNode);
773        }
774      }
775    }
776   
777    void FixTreeOnDelete(TextSegment node, TextSegment parentNode)
778    {
779      Debug.Assert(node == null || node.parent == parentNode);
780      if (parentNode == null)
781        return;
782     
783      // warning: node may be null
784      TextSegment sibling = Sibling(node, parentNode);
785      if (sibling.color == RED) {
786        parentNode.color = RED;
787        sibling.color = BLACK;
788        if (node == parentNode.left) {
789          RotateLeft(parentNode);
790        } else {
791          RotateRight(parentNode);
792        }
793       
794        sibling = Sibling(node, parentNode); // update value of sibling after rotation
795      }
796     
797      if (parentNode.color == BLACK
798          && sibling.color == BLACK
799          && GetColor(sibling.left) == BLACK
800          && GetColor(sibling.right) == BLACK)
801      {
802        sibling.color = RED;
803        FixTreeOnDelete(parentNode, parentNode.parent);
804        return;
805      }
806     
807      if (parentNode.color == RED
808          && sibling.color == BLACK
809          && GetColor(sibling.left) == BLACK
810          && GetColor(sibling.right) == BLACK)
811      {
812        sibling.color = RED;
813        parentNode.color = BLACK;
814        return;
815      }
816     
817      if (node == parentNode.left &&
818          sibling.color == BLACK &&
819          GetColor(sibling.left) == RED &&
820          GetColor(sibling.right) == BLACK)
821      {
822        sibling.color = RED;
823        sibling.left.color = BLACK;
824        RotateRight(sibling);
825      }
826      else if (node == parentNode.right &&
827               sibling.color == BLACK &&
828               GetColor(sibling.right) == RED &&
829               GetColor(sibling.left) == BLACK)
830      {
831        sibling.color = RED;
832        sibling.right.color = BLACK;
833        RotateLeft(sibling);
834      }
835      sibling = Sibling(node, parentNode); // update value of sibling after rotation
836     
837      sibling.color = parentNode.color;
838      parentNode.color = BLACK;
839      if (node == parentNode.left) {
840        if (sibling.right != null) {
841          Debug.Assert(sibling.right.color == RED);
842          sibling.right.color = BLACK;
843        }
844        RotateLeft(parentNode);
845      } else {
846        if (sibling.left != null) {
847          Debug.Assert(sibling.left.color == RED);
848          sibling.left.color = BLACK;
849        }
850        RotateRight(parentNode);
851      }
852    }
853   
854    void ReplaceNode(TextSegment replacedNode, TextSegment newNode)
855    {
856      if (replacedNode.parent == null) {
857        Debug.Assert(replacedNode == root);
858        root = newNode;
859      } else {
860        if (replacedNode.parent.left == replacedNode)
861          replacedNode.parent.left = newNode;
862        else
863          replacedNode.parent.right = newNode;
864      }
865      if (newNode != null) {
866        newNode.parent = replacedNode.parent;
867      }
868      replacedNode.parent = null;
869    }
870   
871    void RotateLeft(TextSegment p)
872    {
873      // let q be p's right child
874      TextSegment q = p.right;
875      Debug.Assert(q != null);
876      Debug.Assert(q.parent == p);
877      // set q to be the new root
878      ReplaceNode(p, q);
879     
880      // set p's right child to be q's left child
881      p.right = q.left;
882      if (p.right != null) p.right.parent = p;
883      // set q's left child to be p
884      q.left = p;
885      p.parent = q;
886      UpdateAugmentedData(p);
887      UpdateAugmentedData(q);
888    }
889   
890    void RotateRight(TextSegment p)
891    {
892      // let q be p's left child
893      TextSegment q = p.left;
894      Debug.Assert(q != null);
895      Debug.Assert(q.parent == p);
896      // set q to be the new root
897      ReplaceNode(p, q);
898     
899      // set p's left child to be q's right child
900      p.left = q.right;
901      if (p.left != null) p.left.parent = p;
902      // set q's right child to be p
903      q.right = p;
904      p.parent = q;
905      UpdateAugmentedData(p);
906      UpdateAugmentedData(q);
907    }
908   
909    static TextSegment Sibling(TextSegment node)
910    {
911      if (node == node.parent.left)
912        return node.parent.right;
913      else
914        return node.parent.left;
915    }
916   
917    static TextSegment Sibling(TextSegment node, TextSegment parentNode)
918    {
919      Debug.Assert(node == null || node.parent == parentNode);
920      if (node == parentNode.left)
921        return parentNode.right;
922      else
923        return parentNode.left;
924    }
925   
926    static bool GetColor(TextSegment node)
927    {
928      return node != null ? node.color : BLACK;
929    }
930    #endregion
931   
932    #region ICollection<T> implementation
933    /// <summary>
934    /// Gets the number of segments in the tree.
935    /// </summary>
936    public int Count {
937      get { return count; }
938    }
939   
940    bool ICollection<T>.IsReadOnly {
941      get { return false; }
942    }
943   
944    /// <summary>
945    /// Gets whether this tree contains the specified item.
946    /// </summary>
947    public bool Contains(T item)
948    {
949      return item != null && item.ownerTree == this;
950    }
951   
952    /// <summary>
953    /// Copies all segments in this SegmentTree to the specified array.
954    /// </summary>
955    public void CopyTo(T[] array, int arrayIndex)
956    {
957      if (array == null)
958        throw new ArgumentNullException("array");
959      if (array.Length < this.Count)
960        throw new ArgumentException("The array is too small", "array");
961      if (arrayIndex < 0 || arrayIndex + count > array.Length)
962        throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - count));
963      foreach (T s in this) {
964        array[arrayIndex++] = s;
965      }
966    }
967   
968    /// <summary>
969    /// Gets an enumerator to enumerate the segments.
970    /// </summary>
971    public IEnumerator<T> GetEnumerator()
972    {
973      if (root != null) {
974        TextSegment current = root.LeftMost;
975        while (current != null) {
976          yield return (T)current;
977          // TODO: check if collection was modified during enumeration
978          current = current.Successor;
979        }
980      }
981    }
982   
983    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
984    {
985      return this.GetEnumerator();
986    }
987    #endregion
988  }
989}
Note: See TracBrowser for help on using the repository browser.