Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Document/DocumentLineTree.cs @ 14706

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

#2077: created branch and added first version

File size: 19.8 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.Diagnostics;
22using System.Text;
23
24namespace ICSharpCode.AvalonEdit.Document
25{
26  using LineNode = DocumentLine;
27 
28  /// <summary>
29  /// Data structure for efficient management of the document lines (most operations are O(lg n)).
30  /// This implements an augmented red-black tree.
31  /// See <see cref="LineNode"/> for the augmented data.
32  ///
33  /// NOTE: The tree is never empty, initially it contains an empty line.
34  /// </summary>
35  sealed class DocumentLineTree : IList<DocumentLine>
36  {
37    #region Constructor
38    readonly TextDocument document;
39    LineNode root;
40   
41    public DocumentLineTree(TextDocument document)
42    {
43      this.document = document;
44     
45      DocumentLine emptyLine = new DocumentLine(document);
46      root = emptyLine.InitLineNode();
47    }
48    #endregion
49   
50    #region Rotation callbacks
51    internal static void UpdateAfterChildrenChange(LineNode node)
52    {
53      int totalCount = 1;
54      int totalLength = node.TotalLength;
55      if (node.left != null) {
56        totalCount += node.left.nodeTotalCount;
57        totalLength += node.left.nodeTotalLength;
58      }
59      if (node.right != null) {
60        totalCount += node.right.nodeTotalCount;
61        totalLength += node.right.nodeTotalLength;
62      }
63      if (totalCount != node.nodeTotalCount
64          || totalLength != node.nodeTotalLength)
65      {
66        node.nodeTotalCount = totalCount;
67        node.nodeTotalLength = totalLength;
68        if (node.parent != null) UpdateAfterChildrenChange(node.parent);
69      }
70    }
71   
72    static void UpdateAfterRotateLeft(LineNode node)
73    {
74      UpdateAfterChildrenChange(node);
75     
76      // not required: rotations only happen on insertions/deletions
77      // -> totalCount changes -> the parent is always updated
78      //UpdateAfterChildrenChange(node.parent);
79    }
80   
81    static void UpdateAfterRotateRight(LineNode node)
82    {
83      UpdateAfterChildrenChange(node);
84     
85      // not required: rotations only happen on insertions/deletions
86      // -> totalCount changes -> the parent is always updated
87      //UpdateAfterChildrenChange(node.parent);
88    }
89    #endregion
90   
91    #region RebuildDocument
92    /// <summary>
93    /// Rebuild the tree, in O(n).
94    /// </summary>
95    public void RebuildTree(List<DocumentLine> documentLines)
96    {
97      LineNode[] nodes = new LineNode[documentLines.Count];
98      for (int i = 0; i < documentLines.Count; i++) {
99        DocumentLine ls = documentLines[i];
100        LineNode node = ls.InitLineNode();
101        nodes[i] = node;
102      }
103      Debug.Assert(nodes.Length > 0);
104      // now build the corresponding balanced tree
105      int height = GetTreeHeight(nodes.Length);
106      Debug.WriteLine("DocumentLineTree will have height: " + height);
107      root = BuildTree(nodes, 0, nodes.Length, height);
108      root.color = BLACK;
109      #if DEBUG
110      CheckProperties();
111      #endif
112    }
113   
114    internal static int GetTreeHeight(int size)
115    {
116      if (size == 0)
117        return 0;
118      else
119        return GetTreeHeight(size / 2) + 1;
120    }
121   
122    /// <summary>
123    /// build a tree from a list of nodes
124    /// </summary>
125    LineNode BuildTree(LineNode[] nodes, int start, int end, int subtreeHeight)
126    {
127      Debug.Assert(start <= end);
128      if (start == end) {
129        return null;
130      }
131      int middle = (start + end) / 2;
132      LineNode node = nodes[middle];
133      node.left = BuildTree(nodes, start, middle, subtreeHeight - 1);
134      node.right = BuildTree(nodes, middle + 1, end, subtreeHeight - 1);
135      if (node.left != null) node.left.parent = node;
136      if (node.right != null) node.right.parent = node;
137      if (subtreeHeight == 1)
138        node.color = RED;
139      UpdateAfterChildrenChange(node);
140      return node;
141    }
142    #endregion
143   
144    #region GetNodeBy... / Get...FromNode
145    LineNode GetNodeByIndex(int index)
146    {
147      Debug.Assert(index >= 0);
148      Debug.Assert(index < root.nodeTotalCount);
149      LineNode node = root;
150      while (true) {
151        if (node.left != null && index < node.left.nodeTotalCount) {
152          node = node.left;
153        } else {
154          if (node.left != null) {
155            index -= node.left.nodeTotalCount;
156          }
157          if (index == 0)
158            return node;
159          index--;
160          node = node.right;
161        }
162      }
163    }
164   
165    internal static int GetIndexFromNode(LineNode node)
166    {
167      int index = (node.left != null) ? node.left.nodeTotalCount : 0;
168      while (node.parent != null) {
169        if (node == node.parent.right) {
170          if (node.parent.left != null)
171            index += node.parent.left.nodeTotalCount;
172          index++;
173        }
174        node = node.parent;
175      }
176      return index;
177    }
178   
179    LineNode GetNodeByOffset(int offset)
180    {
181      Debug.Assert(offset >= 0);
182      Debug.Assert(offset <= root.nodeTotalLength);
183      if (offset == root.nodeTotalLength) {
184        return root.RightMost;
185      }
186      LineNode node = root;
187      while (true) {
188        if (node.left != null && offset < node.left.nodeTotalLength) {
189          node = node.left;
190        } else {
191          if (node.left != null) {
192            offset -= node.left.nodeTotalLength;
193          }
194          offset -= node.TotalLength;
195          if (offset < 0)
196            return node;
197          node = node.right;
198        }
199      }
200    }
201   
202    internal static int GetOffsetFromNode(LineNode node)
203    {
204      int offset = (node.left != null) ? node.left.nodeTotalLength : 0;
205      while (node.parent != null) {
206        if (node == node.parent.right) {
207          if (node.parent.left != null)
208            offset += node.parent.left.nodeTotalLength;
209          offset += node.parent.TotalLength;
210        }
211        node = node.parent;
212      }
213      return offset;
214    }
215    #endregion
216   
217    #region GetLineBy
218    public DocumentLine GetByNumber(int number)
219    {
220      return GetNodeByIndex(number - 1);
221    }
222   
223    public DocumentLine GetByOffset(int offset)
224    {
225      return GetNodeByOffset(offset);
226    }
227    #endregion
228   
229    #region LineCount
230    public int LineCount {
231      get {
232        return root.nodeTotalCount;
233      }
234    }
235    #endregion
236   
237    #region CheckProperties
238    #if DEBUG
239    [Conditional("DATACONSISTENCYTEST")]
240    internal void CheckProperties()
241    {
242      Debug.Assert(root.nodeTotalLength == document.TextLength);
243      CheckProperties(root);
244     
245      // check red-black property:
246      int blackCount = -1;
247      CheckNodeProperties(root, null, RED, 0, ref blackCount);
248    }
249   
250    void CheckProperties(LineNode node)
251    {
252      int totalCount = 1;
253      int totalLength = node.TotalLength;
254      if (node.left != null) {
255        CheckProperties(node.left);
256        totalCount += node.left.nodeTotalCount;
257        totalLength += node.left.nodeTotalLength;
258      }
259      if (node.right != null) {
260        CheckProperties(node.right);
261        totalCount += node.right.nodeTotalCount;
262        totalLength += node.right.nodeTotalLength;
263      }
264      Debug.Assert(node.nodeTotalCount == totalCount);
265      Debug.Assert(node.nodeTotalLength == totalLength);
266    }
267   
268    /*
269    1. A node is either red or black.
270    2. The root is black.
271    3. All leaves are black. (The leaves are the NIL children.)
272    4. Both children of every red node are black. (So every red node must have a black parent.)
273    5. Every simple path from a node to a descendant leaf contains the same number of black nodes. (Not counting the leaf node.)
274     */
275    void CheckNodeProperties(LineNode node, LineNode parentNode, bool parentColor, int blackCount, ref int expectedBlackCount)
276    {
277      if (node == null) return;
278     
279      Debug.Assert(node.parent == parentNode);
280     
281      if (parentColor == RED) {
282        Debug.Assert(node.color == BLACK);
283      }
284      if (node.color == BLACK) {
285        blackCount++;
286      }
287      if (node.left == null && node.right == null) {
288        // node is a leaf node:
289        if (expectedBlackCount == -1)
290          expectedBlackCount = blackCount;
291        else
292          Debug.Assert(expectedBlackCount == blackCount);
293      }
294      CheckNodeProperties(node.left, node, node.color, blackCount, ref expectedBlackCount);
295      CheckNodeProperties(node.right, node, node.color, blackCount, ref expectedBlackCount);
296    }
297   
298    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
299    public string GetTreeAsString()
300    {
301      StringBuilder b = new StringBuilder();
302      AppendTreeToString(root, b, 0);
303      return b.ToString();
304    }
305   
306    static void AppendTreeToString(LineNode node, StringBuilder b, int indent)
307    {
308      if (node.color == RED)
309        b.Append("RED   ");
310      else
311        b.Append("BLACK ");
312      b.AppendLine(node.ToString());
313      indent += 2;
314      if (node.left != null) {
315        b.Append(' ', indent);
316        b.Append("L: ");
317        AppendTreeToString(node.left, b, indent);
318      }
319      if (node.right != null) {
320        b.Append(' ', indent);
321        b.Append("R: ");
322        AppendTreeToString(node.right, b, indent);
323      }
324    }
325    #endif
326    #endregion
327   
328    #region Insert/Remove lines
329    public void RemoveLine(DocumentLine line)
330    {
331      RemoveNode(line);
332      line.isDeleted = true;
333    }
334   
335    public DocumentLine InsertLineAfter(DocumentLine line, int totalLength)
336    {
337      DocumentLine newLine = new DocumentLine(document);
338      newLine.TotalLength = totalLength;
339     
340      InsertAfter(line, newLine);
341      return newLine;
342    }
343   
344    void InsertAfter(LineNode node, DocumentLine newLine)
345    {
346      LineNode newNode = newLine.InitLineNode();
347      if (node.right == null) {
348        InsertAsRight(node, newNode);
349      } else {
350        InsertAsLeft(node.right.LeftMost, newNode);
351      }
352    }
353    #endregion
354   
355    #region Red/Black Tree
356    internal const bool RED = true;
357    internal const bool BLACK = false;
358   
359    void InsertAsLeft(LineNode parentNode, LineNode newNode)
360    {
361      Debug.Assert(parentNode.left == null);
362      parentNode.left = newNode;
363      newNode.parent = parentNode;
364      newNode.color = RED;
365      UpdateAfterChildrenChange(parentNode);
366      FixTreeOnInsert(newNode);
367    }
368   
369    void InsertAsRight(LineNode parentNode, LineNode newNode)
370    {
371      Debug.Assert(parentNode.right == null);
372      parentNode.right = newNode;
373      newNode.parent = parentNode;
374      newNode.color = RED;
375      UpdateAfterChildrenChange(parentNode);
376      FixTreeOnInsert(newNode);
377    }
378   
379    void FixTreeOnInsert(LineNode node)
380    {
381      Debug.Assert(node != null);
382      Debug.Assert(node.color == RED);
383      Debug.Assert(node.left == null || node.left.color == BLACK);
384      Debug.Assert(node.right == null || node.right.color == BLACK);
385     
386      LineNode parentNode = node.parent;
387      if (parentNode == null) {
388        // we inserted in the root -> the node must be black
389        // since this is a root node, making the node black increments the number of black nodes
390        // on all paths by one, so it is still the same for all paths.
391        node.color = BLACK;
392        return;
393      }
394      if (parentNode.color == BLACK) {
395        // if the parent node where we inserted was black, our red node is placed correctly.
396        // since we inserted a red node, the number of black nodes on each path is unchanged
397        // -> the tree is still balanced
398        return;
399      }
400      // parentNode is red, so there is a conflict here!
401     
402      // because the root is black, parentNode is not the root -> there is a grandparent node
403      LineNode grandparentNode = parentNode.parent;
404      LineNode uncleNode = Sibling(parentNode);
405      if (uncleNode != null && uncleNode.color == RED) {
406        parentNode.color = BLACK;
407        uncleNode.color = BLACK;
408        grandparentNode.color = RED;
409        FixTreeOnInsert(grandparentNode);
410        return;
411      }
412      // now we know: parent is red but uncle is black
413      // First rotation:
414      if (node == parentNode.right && parentNode == grandparentNode.left) {
415        RotateLeft(parentNode);
416        node = node.left;
417      } else if (node == parentNode.left && parentNode == grandparentNode.right) {
418        RotateRight(parentNode);
419        node = node.right;
420      }
421      // because node might have changed, reassign variables:
422      parentNode = node.parent;
423      grandparentNode = parentNode.parent;
424     
425      // Now recolor a bit:
426      parentNode.color = BLACK;
427      grandparentNode.color = RED;
428      // Second rotation:
429      if (node == parentNode.left && parentNode == grandparentNode.left) {
430        RotateRight(grandparentNode);
431      } else {
432        // because of the first rotation, this is guaranteed:
433        Debug.Assert(node == parentNode.right && parentNode == grandparentNode.right);
434        RotateLeft(grandparentNode);
435      }
436    }
437   
438    void RemoveNode(LineNode removedNode)
439    {
440      if (removedNode.left != null && removedNode.right != null) {
441        // replace removedNode with it's in-order successor
442       
443        LineNode leftMost = removedNode.right.LeftMost;
444        RemoveNode(leftMost); // remove leftMost from its current location
445       
446        // and overwrite the removedNode with it
447        ReplaceNode(removedNode, leftMost);
448        leftMost.left = removedNode.left;
449        if (leftMost.left != null) leftMost.left.parent = leftMost;
450        leftMost.right = removedNode.right;
451        if (leftMost.right != null) leftMost.right.parent = leftMost;
452        leftMost.color = removedNode.color;
453       
454        UpdateAfterChildrenChange(leftMost);
455        if (leftMost.parent != null) UpdateAfterChildrenChange(leftMost.parent);
456        return;
457      }
458     
459      // now either removedNode.left or removedNode.right is null
460      // get the remaining child
461      LineNode parentNode = removedNode.parent;
462      LineNode childNode = removedNode.left ?? removedNode.right;
463      ReplaceNode(removedNode, childNode);
464      if (parentNode != null) UpdateAfterChildrenChange(parentNode);
465      if (removedNode.color == BLACK) {
466        if (childNode != null && childNode.color == RED) {
467          childNode.color = BLACK;
468        } else {
469          FixTreeOnDelete(childNode, parentNode);
470        }
471      }
472    }
473   
474    void FixTreeOnDelete(LineNode node, LineNode parentNode)
475    {
476      Debug.Assert(node == null || node.parent == parentNode);
477      if (parentNode == null)
478        return;
479     
480      // warning: node may be null
481      LineNode sibling = Sibling(node, parentNode);
482      if (sibling.color == RED) {
483        parentNode.color = RED;
484        sibling.color = BLACK;
485        if (node == parentNode.left) {
486          RotateLeft(parentNode);
487        } else {
488          RotateRight(parentNode);
489        }
490       
491        sibling = Sibling(node, parentNode); // update value of sibling after rotation
492      }
493     
494      if (parentNode.color == BLACK
495          && sibling.color == BLACK
496          && GetColor(sibling.left) == BLACK
497          && GetColor(sibling.right) == BLACK)
498      {
499        sibling.color = RED;
500        FixTreeOnDelete(parentNode, parentNode.parent);
501        return;
502      }
503     
504      if (parentNode.color == RED
505          && sibling.color == BLACK
506          && GetColor(sibling.left) == BLACK
507          && GetColor(sibling.right) == BLACK)
508      {
509        sibling.color = RED;
510        parentNode.color = BLACK;
511        return;
512      }
513     
514      if (node == parentNode.left &&
515          sibling.color == BLACK &&
516          GetColor(sibling.left) == RED &&
517          GetColor(sibling.right) == BLACK)
518      {
519        sibling.color = RED;
520        sibling.left.color = BLACK;
521        RotateRight(sibling);
522      }
523      else if (node == parentNode.right &&
524               sibling.color == BLACK &&
525               GetColor(sibling.right) == RED &&
526               GetColor(sibling.left) == BLACK)
527      {
528        sibling.color = RED;
529        sibling.right.color = BLACK;
530        RotateLeft(sibling);
531      }
532      sibling = Sibling(node, parentNode); // update value of sibling after rotation
533     
534      sibling.color = parentNode.color;
535      parentNode.color = BLACK;
536      if (node == parentNode.left) {
537        if (sibling.right != null) {
538          Debug.Assert(sibling.right.color == RED);
539          sibling.right.color = BLACK;
540        }
541        RotateLeft(parentNode);
542      } else {
543        if (sibling.left != null) {
544          Debug.Assert(sibling.left.color == RED);
545          sibling.left.color = BLACK;
546        }
547        RotateRight(parentNode);
548      }
549    }
550   
551    void ReplaceNode(LineNode replacedNode, LineNode newNode)
552    {
553      if (replacedNode.parent == null) {
554        Debug.Assert(replacedNode == root);
555        root = newNode;
556      } else {
557        if (replacedNode.parent.left == replacedNode)
558          replacedNode.parent.left = newNode;
559        else
560          replacedNode.parent.right = newNode;
561      }
562      if (newNode != null) {
563        newNode.parent = replacedNode.parent;
564      }
565      replacedNode.parent = null;
566    }
567   
568    void RotateLeft(LineNode p)
569    {
570      // let q be p's right child
571      LineNode q = p.right;
572      Debug.Assert(q != null);
573      Debug.Assert(q.parent == p);
574      // set q to be the new root
575      ReplaceNode(p, q);
576     
577      // set p's right child to be q's left child
578      p.right = q.left;
579      if (p.right != null) p.right.parent = p;
580      // set q's left child to be p
581      q.left = p;
582      p.parent = q;
583      UpdateAfterRotateLeft(p);
584    }
585   
586    void RotateRight(LineNode p)
587    {
588      // let q be p's left child
589      LineNode q = p.left;
590      Debug.Assert(q != null);
591      Debug.Assert(q.parent == p);
592      // set q to be the new root
593      ReplaceNode(p, q);
594     
595      // set p's left child to be q's right child
596      p.left = q.right;
597      if (p.left != null) p.left.parent = p;
598      // set q's right child to be p
599      q.right = p;
600      p.parent = q;
601      UpdateAfterRotateRight(p);
602    }
603   
604    static LineNode Sibling(LineNode node)
605    {
606      if (node == node.parent.left)
607        return node.parent.right;
608      else
609        return node.parent.left;
610    }
611   
612    static LineNode Sibling(LineNode node, LineNode parentNode)
613    {
614      Debug.Assert(node == null || node.parent == parentNode);
615      if (node == parentNode.left)
616        return parentNode.right;
617      else
618        return parentNode.left;
619    }
620   
621    static bool GetColor(LineNode node)
622    {
623      return node != null ? node.color : BLACK;
624    }
625    #endregion
626   
627    #region IList implementation
628    DocumentLine IList<DocumentLine>.this[int index] {
629      get {
630        document.VerifyAccess();
631        return GetByNumber(1 + index);
632      }
633      set {
634        throw new NotSupportedException();
635      }
636    }
637   
638    int ICollection<DocumentLine>.Count {
639      get {
640        document.VerifyAccess();
641        return LineCount;
642      }
643    }
644   
645    bool ICollection<DocumentLine>.IsReadOnly {
646      get { return true; }
647    }
648   
649    int IList<DocumentLine>.IndexOf(DocumentLine item)
650    {
651      document.VerifyAccess();
652      if (item == null || item.IsDeleted)
653        return -1;
654      int index = item.LineNumber - 1;
655      if (index < LineCount && GetNodeByIndex(index) == item)
656        return index;
657      else
658        return -1;
659    }
660   
661    void IList<DocumentLine>.Insert(int index, DocumentLine item)
662    {
663      throw new NotSupportedException();
664    }
665   
666    void IList<DocumentLine>.RemoveAt(int index)
667    {
668      throw new NotSupportedException();
669    }
670   
671    void ICollection<DocumentLine>.Add(DocumentLine item)
672    {
673      throw new NotSupportedException();
674    }
675   
676    void ICollection<DocumentLine>.Clear()
677    {
678      throw new NotSupportedException();
679    }
680   
681    bool ICollection<DocumentLine>.Contains(DocumentLine item)
682    {
683      IList<DocumentLine> self = this;
684      return self.IndexOf(item) >= 0;
685    }
686   
687    void ICollection<DocumentLine>.CopyTo(DocumentLine[] array, int arrayIndex)
688    {
689      if (array == null)
690        throw new ArgumentNullException("array");
691      if (array.Length < LineCount)
692        throw new ArgumentException("The array is too small", "array");
693      if (arrayIndex < 0 || arrayIndex + LineCount > array.Length)
694        throw new ArgumentOutOfRangeException("arrayIndex", arrayIndex, "Value must be between 0 and " + (array.Length - LineCount));
695      foreach (DocumentLine ls in this) {
696        array[arrayIndex++] = ls;
697      }
698    }
699   
700    bool ICollection<DocumentLine>.Remove(DocumentLine item)
701    {
702      throw new NotSupportedException();
703    }
704   
705    public IEnumerator<DocumentLine> GetEnumerator()
706    {
707      document.VerifyAccess();
708      return Enumerate();
709    }
710   
711    IEnumerator<DocumentLine> Enumerate()
712    {
713      document.VerifyAccess();
714      DocumentLine line = root.LeftMost;
715      while (line != null) {
716        yield return line;
717        line = line.NextLine;
718      }
719    }
720   
721    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
722    {
723      return this.GetEnumerator();
724    }
725    #endregion
726  }
727}
Note: See TracBrowser for help on using the repository browser.