Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.AvalonEdit/5.0.1/AvalonEdit-5.0.1/Utils/RopeNode.cs @ 15377

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

#2077: created branch and added first version

File size: 20.4 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.Diagnostics;
21using System.Runtime.Serialization;
22
23using System.Text;
24
25namespace ICSharpCode.AvalonEdit.Utils
26{
27  // Class used to represent a node in the tree.
28  // There are three types of nodes:
29  // Concat nodes: height>0, left!=null, right!=null, contents==null
30  // Leaf nodes: height==0, left==null, right==null, contents!=null
31  // Function nodes: height==0, left==null, right==null, contents==null, are of type FunctionNode<T>
32 
33  [Serializable]
34  class RopeNode<T>
35  {
36    internal const int NodeSize = 256;
37   
38    internal static readonly RopeNode<T> emptyRopeNode = new RopeNode<T> { isShared = true, contents = new T[RopeNode<T>.NodeSize] };
39   
40    // Fields for pointers to sub-nodes. Only non-null for concat nodes (height>=1)
41    internal RopeNode<T> left, right;
42    internal volatile bool isShared; // specifies whether this node is shared between multiple ropes
43    // the total length of all text in this subtree
44    internal int length;
45    // the height of this subtree: 0 for leaf nodes; 1+max(left.height,right.height) for concat nodes
46    internal byte height;
47   
48    // The character data. Only non-null for leaf nodes (height=0) that aren't function nodes.
49    internal T[] contents;
50   
51    internal int Balance {
52      get { return right.height - left.height; }
53    }
54   
55    [Conditional("DATACONSISTENCYTEST")]
56    internal void CheckInvariants()
57    {
58      if (height == 0) {
59        Debug.Assert(left == null && right == null);
60        if (contents == null) {
61          Debug.Assert(this is FunctionNode<T>);
62          Debug.Assert(length > 0);
63          Debug.Assert(isShared);
64        } else {
65          Debug.Assert(contents != null && contents.Length == NodeSize);
66          Debug.Assert(length >= 0 && length <= NodeSize);
67        }
68      } else {
69        Debug.Assert(left != null && right != null);
70        Debug.Assert(contents == null);
71        Debug.Assert(length == left.length + right.length);
72        Debug.Assert(height == 1 + Math.Max(left.height, right.height));
73        Debug.Assert(Math.Abs(this.Balance) <= 1);
74       
75        // this is an additional invariant that forces the tree to combine small leafs to prevent excessive memory usage:
76        Debug.Assert(length > NodeSize);
77        // note that this invariant ensures that all nodes except for the empty rope's single node have at least length 1
78       
79        if (isShared)
80          Debug.Assert(left.isShared && right.isShared);
81        left.CheckInvariants();
82        right.CheckInvariants();
83      }
84    }
85   
86    internal RopeNode<T> Clone()
87    {
88      if (height == 0) {
89        // If a function node needs cloning, we'll evaluate it.
90        if (contents == null)
91          return GetContentNode().Clone();
92        T[] newContents = new T[NodeSize];
93        contents.CopyTo(newContents, 0);
94        return new RopeNode<T> {
95          length = this.length,
96          contents = newContents
97        };
98      } else {
99        return new RopeNode<T> {
100          left = this.left,
101          right = this.right,
102          length = this.length,
103          height = this.height
104        };
105      }
106    }
107   
108    internal RopeNode<T> CloneIfShared()
109    {
110      if (isShared)
111        return Clone();
112      else
113        return this;
114    }
115   
116    internal void Publish()
117    {
118      if (!isShared) {
119        if (left != null)
120          left.Publish();
121        if (right != null)
122          right.Publish();
123        // it's important that isShared=true is set at the end:
124        // Publish() must not return until the whole subtree is marked as shared, even when
125        // Publish() is called concurrently.
126        isShared = true;
127      }
128    }
129   
130    internal static RopeNode<T> CreateFromArray(T[] arr, int index, int length)
131    {
132      if (length == 0) {
133        return emptyRopeNode;
134      }
135      RopeNode<T> node = CreateNodes(length);
136      return node.StoreElements(0, arr, index, length);
137    }
138   
139    internal static RopeNode<T> CreateNodes(int totalLength)
140    {
141      int leafCount = (totalLength + NodeSize - 1) / NodeSize;
142      return CreateNodes(leafCount, totalLength);
143    }
144   
145    static RopeNode<T> CreateNodes(int leafCount, int totalLength)
146    {
147      Debug.Assert(leafCount > 0);
148      Debug.Assert(totalLength > 0);
149      RopeNode<T> result = new RopeNode<T>();
150      result.length = totalLength;
151      if (leafCount == 1) {
152        result.contents = new T[NodeSize];
153      } else {
154        int rightSide = leafCount / 2;
155        int leftSide = leafCount - rightSide;
156        int leftLength = leftSide * NodeSize;
157        result.left = CreateNodes(leftSide, leftLength);
158        result.right = CreateNodes(rightSide, totalLength - leftLength);
159        result.height = (byte)(1 + Math.Max(result.left.height, result.right.height));
160      }
161      return result;
162    }
163   
164    /// <summary>
165    /// Balances this node and recomputes the 'height' field.
166    /// This method assumes that the children of this node are already balanced and have an up-to-date 'height' value.
167    /// </summary>
168    internal void Rebalance()
169    {
170      // Rebalance() shouldn't be called on shared nodes - it's only called after modifications!
171      Debug.Assert(!isShared);
172      // leaf nodes are always balanced (we don't use 'height' to detect leaf nodes here
173      // because Balance is supposed to recompute the height).
174      if (left == null)
175        return;
176     
177      // ensure we didn't miss a MergeIfPossible step
178      Debug.Assert(this.length > NodeSize);
179     
180      // We need to loop until it's balanced. Rotations might cause two small leaves to combine to a larger one,
181      // which changes the height and might mean we need additional balancing steps.
182      while (Math.Abs(this.Balance) > 1) {
183        // AVL balancing
184        // note: because we don't care about the identity of concat nodes, this works a little different than usual
185        // tree rotations: in our implementation, the "this" node will stay at the top, only its children are rearranged
186        if (this.Balance > 1) {
187          if (right.Balance < 0) {
188            right = right.CloneIfShared();
189            right.RotateRight();
190          }
191          this.RotateLeft();
192          // If 'this' was unbalanced by more than 2, we've shifted some of the inbalance to the left node; so rebalance that.
193          this.left.Rebalance();
194        } else if (this.Balance < -1) {
195          if (left.Balance > 0) {
196            left = left.CloneIfShared();
197            left.RotateLeft();
198          }
199          this.RotateRight();
200          // If 'this' was unbalanced by more than 2, we've shifted some of the inbalance to the right node; so rebalance that.
201          this.right.Rebalance();
202        }
203      }
204     
205      Debug.Assert(Math.Abs(this.Balance) <= 1);
206      this.height = (byte)(1 + Math.Max(left.height, right.height));
207    }
208   
209    void RotateLeft()
210    {
211      Debug.Assert(!isShared);
212     
213      /* Rotate tree to the left
214       *
215       *       this               this
216       *       /  \               /  \
217       *      A   right   ===>  left  C
218       *           / \          / \
219       *          B   C        A   B
220       */
221      RopeNode<T> a = left;
222      RopeNode<T> b = right.left;
223      RopeNode<T> c = right.right;
224      // reuse right concat node, if possible
225      this.left = right.isShared ? new RopeNode<T>() : right;
226      this.left.left = a;
227      this.left.right = b;
228      this.left.length = a.length + b.length;
229      this.left.height = (byte)(1 + Math.Max(a.height, b.height));
230      this.right = c;
231     
232      this.left.MergeIfPossible();
233    }
234   
235    void RotateRight()
236    {
237      Debug.Assert(!isShared);
238     
239      /* Rotate tree to the right
240       *
241       *       this             this
242       *       /  \             /  \
243       *     left  C   ===>    A  right
244       *     / \                   /  \
245       *    A   B                 B    C
246       */
247      RopeNode<T> a = left.left;
248      RopeNode<T> b = left.right;
249      RopeNode<T> c = right;
250      // reuse left concat node, if possible
251      this.right = left.isShared ? new RopeNode<T>() : left;
252      this.right.left = b;
253      this.right.right = c;
254      this.right.length = b.length + c.length;
255      this.right.height = (byte)(1 + Math.Max(b.height, c.height));
256      this.left = a;
257     
258      this.right.MergeIfPossible();
259    }
260   
261    void MergeIfPossible()
262    {
263      Debug.Assert(!isShared);
264     
265      if (this.length <= NodeSize) {
266        // Convert this concat node to leaf node.
267        // We know left and right cannot be concat nodes (they would have merged already),
268        // but they could be function nodes.
269        this.height = 0;
270        int lengthOnLeftSide = this.left.length;
271        if (this.left.isShared) {
272          this.contents = new T[NodeSize];
273          left.CopyTo(0, this.contents, 0, lengthOnLeftSide);
274        } else {
275          // must be a leaf node: function nodes are always marked shared
276          Debug.Assert(this.left.contents != null);
277          // steal buffer from left side
278          this.contents = this.left.contents;
279          #if DEBUG
280          // In debug builds, explicitly mark left node as 'damaged' - but no one else should be using it
281          // because it's not shared.
282          this.left.contents = Empty<T>.Array;
283          #endif
284        }
285        this.left = null;
286        right.CopyTo(0, this.contents, lengthOnLeftSide, this.right.length);
287        this.right = null;
288      }
289    }
290   
291    /// <summary>
292    /// Copies from the array to this node.
293    /// </summary>
294    internal RopeNode<T> StoreElements(int index, T[] array, int arrayIndex, int count)
295    {
296      RopeNode<T> result = this.CloneIfShared();
297      // result cannot be function node after a call to Clone()
298      if (result.height == 0) {
299        // leaf node:
300        Array.Copy(array, arrayIndex, result.contents, index, count);
301      } else {
302        // concat node:
303        if (index + count <= result.left.length) {
304          result.left = result.left.StoreElements(index, array, arrayIndex, count);
305        } else if (index >= this.left.length) {
306          result.right = result.right.StoreElements(index - result.left.length, array, arrayIndex, count);
307        } else {
308          int amountInLeft = result.left.length - index;
309          result.left = result.left.StoreElements(index, array, arrayIndex, amountInLeft);
310          result.right = result.right.StoreElements(0, array, arrayIndex + amountInLeft, count - amountInLeft);
311        }
312        result.Rebalance(); // tree layout might have changed if function nodes were replaced with their content
313      }
314      return result;
315    }
316   
317    /// <summary>
318    /// Copies from this node to the array.
319    /// </summary>
320    internal void CopyTo(int index, T[] array, int arrayIndex, int count)
321    {
322      if (height == 0) {
323        if (this.contents == null) {
324          // function node
325          this.GetContentNode().CopyTo(index, array, arrayIndex, count);
326        } else {
327          // leaf node
328          Array.Copy(this.contents, index, array, arrayIndex, count);
329        }
330      } else {
331        // concat node
332        if (index + count <= this.left.length) {
333          this.left.CopyTo(index, array, arrayIndex, count);
334        } else if (index >= this.left.length) {
335          this.right.CopyTo(index - this.left.length, array, arrayIndex, count);
336        } else {
337          int amountInLeft = this.left.length - index;
338          this.left.CopyTo(index, array, arrayIndex, amountInLeft);
339          this.right.CopyTo(0, array, arrayIndex + amountInLeft, count - amountInLeft);
340        }
341      }
342    }
343   
344    internal RopeNode<T> SetElement(int offset, T value)
345    {
346      RopeNode<T> result = CloneIfShared();
347      // result of CloneIfShared() is leaf or concat node
348      if (result.height == 0) {
349        result.contents[offset] = value;
350      } else {
351        if (offset < result.left.length) {
352          result.left = result.left.SetElement(offset, value);
353        } else {
354          result.right = result.right.SetElement(offset - result.left.length, value);
355        }
356        result.Rebalance(); // tree layout might have changed if function nodes were replaced with their content
357      }
358      return result;
359    }
360   
361    internal static RopeNode<T> Concat(RopeNode<T> left, RopeNode<T> right)
362    {
363      if (left.length == 0)
364        return right;
365      if (right.length == 0)
366        return left;
367     
368      if (left.length + right.length <= NodeSize) {
369        left = left.CloneIfShared();
370        // left is guaranteed to be leaf node after cloning:
371        // - it cannot be function node (due to clone)
372        // - it cannot be concat node (too short)
373        right.CopyTo(0, left.contents, left.length, right.length);
374        left.length += right.length;
375        return left;
376      } else {
377        RopeNode<T> concatNode = new RopeNode<T>();
378        concatNode.left = left;
379        concatNode.right = right;
380        concatNode.length = left.length + right.length;
381        concatNode.Rebalance();
382        return concatNode;
383      }
384    }
385   
386    /// <summary>
387    /// Splits this leaf node at offset and returns a new node with the part of the text after offset.
388    /// </summary>
389    RopeNode<T> SplitAfter(int offset)
390    {
391      Debug.Assert(!isShared && height == 0 && contents != null);
392      RopeNode<T> newPart = new RopeNode<T>();
393      newPart.contents = new T[NodeSize];
394      newPart.length = this.length - offset;
395      Array.Copy(this.contents, offset, newPart.contents, 0, newPart.length);
396      this.length = offset;
397      return newPart;
398    }
399   
400    internal RopeNode<T> Insert(int offset, RopeNode<T> newElements)
401    {
402      if (offset == 0) {
403        return Concat(newElements, this);
404      } else if (offset == this.length) {
405        return Concat(this, newElements);
406      }
407     
408      // first clone this node (converts function nodes to leaf or concat nodes)
409      RopeNode<T> result = CloneIfShared();
410      if (result.height == 0) {
411        // leaf node: we'll need to split this node
412        RopeNode<T> left = result;
413        RopeNode<T> right = left.SplitAfter(offset);
414        return Concat(Concat(left, newElements), right);
415      } else {
416        // concat node
417        if (offset < result.left.length) {
418          result.left = result.left.Insert(offset, newElements);
419        } else {
420          result.right = result.right.Insert(offset - result.left.length, newElements);
421        }
422        result.length += newElements.length;
423        result.Rebalance();
424        return result;
425      }
426    }
427   
428    internal RopeNode<T> Insert(int offset, T[] array, int arrayIndex, int count)
429    {
430      Debug.Assert(count > 0);
431     
432      if (this.length + count < RopeNode<char>.NodeSize) {
433        RopeNode<T> result = CloneIfShared();
434        // result must be leaf node (Clone never returns function nodes, too short for concat node)
435        int lengthAfterOffset = result.length - offset;
436        T[] resultContents = result.contents;
437        for (int i = lengthAfterOffset; i >= 0; i--) {
438          resultContents[i + offset + count] = resultContents[i + offset];
439        }
440        Array.Copy(array, arrayIndex, resultContents, offset, count);
441        result.length += count;
442        return result;
443      } else if (height == 0) {
444        // TODO: implement this more efficiently?
445        return Insert(offset, CreateFromArray(array, arrayIndex, count));
446      } else {
447        // this is a concat node (both leafs and function nodes are handled by the case above)
448        RopeNode<T> result = CloneIfShared();
449        if (offset < result.left.length) {
450          result.left = result.left.Insert(offset, array, arrayIndex, count);
451        } else {
452          result.right = result.right.Insert(offset - result.left.length, array, arrayIndex, count);
453        }
454        result.length += count;
455        result.Rebalance();
456        return result;
457      }
458    }
459   
460    internal RopeNode<T> RemoveRange(int index, int count)
461    {
462      Debug.Assert(count > 0);
463     
464      // produce empty node when one node is deleted completely
465      if (index == 0 && count == this.length)
466        return emptyRopeNode;
467     
468      int endIndex = index + count;
469      RopeNode<T> result = CloneIfShared(); // convert function node to concat/leaf
470      if (result.height == 0) {
471        int remainingAfterEnd = result.length - endIndex;
472        for (int i = 0; i < remainingAfterEnd; i++) {
473          result.contents[index + i] = result.contents[endIndex + i];
474        }
475        result.length -= count;
476      } else {
477        if (endIndex <= result.left.length) {
478          // deletion is only within the left part
479          result.left = result.left.RemoveRange(index, count);
480        } else if (index >= result.left.length) {
481          // deletion is only within the right part
482          result.right = result.right.RemoveRange(index - result.left.length, count);
483        } else {
484          // deletion overlaps both parts
485          int deletionAmountOnLeftSide = result.left.length - index;
486          result.left = result.left.RemoveRange(index, deletionAmountOnLeftSide);
487          result.right = result.right.RemoveRange(0, count - deletionAmountOnLeftSide);
488        }
489        // The deletion might have introduced empty nodes. Those must be removed.
490        if (result.left.length == 0)
491          return result.right;
492        if (result.right.length == 0)
493          return result.left;
494       
495        result.length -= count;
496        result.MergeIfPossible();
497        result.Rebalance();
498      }
499      return result;
500    }
501   
502    #region Debug Output
503    #if DEBUG
504    internal virtual void AppendTreeToString(StringBuilder b, int indent)
505    {
506      b.AppendLine(ToString());
507      indent += 2;
508      if (left != null) {
509        b.Append(' ', indent);
510        b.Append("L: ");
511        left.AppendTreeToString(b, indent);
512      }
513      if (right != null) {
514        b.Append(' ', indent);
515        b.Append("R: ");
516        right.AppendTreeToString(b, indent);
517      }
518    }
519   
520    public override string ToString()
521    {
522      if (contents != null) {
523        char[] charContents = contents as char[];
524        if (charContents != null)
525          return "[Leaf length=" + length + ", isShared=" + isShared + ", text=\"" + new string(charContents, 0, length) + "\"]";
526        else
527          return "[Leaf length=" + length + ", isShared=" + isShared + "\"]";
528      } else {
529        return "[Concat length=" + length + ", isShared=" + isShared + ", height=" + height + ", Balance=" + this.Balance + "]";
530      }
531    }
532   
533    internal string GetTreeAsString()
534    {
535      StringBuilder b = new StringBuilder();
536      AppendTreeToString(b, 0);
537      return b.ToString();
538    }
539    #endif
540    #endregion
541   
542    /// <summary>
543    /// Gets the root node of the subtree from a lazily evaluated function node.
544    /// Such nodes are always marked as shared.
545    /// GetContentNode() will return either a Concat or Leaf node, never another FunctionNode.
546    /// </summary>
547    internal virtual RopeNode<T> GetContentNode()
548    {
549      throw new InvalidOperationException("Called GetContentNode() on non-FunctionNode.");
550    }
551  }
552 
553  sealed class FunctionNode<T> : RopeNode<T>
554  {
555    Func<Rope<T>> initializer;
556    RopeNode<T> cachedResults;
557   
558    public FunctionNode(int length, Func<Rope<T>> initializer)
559    {
560      Debug.Assert(length > 0);
561      Debug.Assert(initializer != null);
562     
563      this.length = length;
564      this.initializer = initializer;
565      // Function nodes are immediately shared, but cannot be cloned.
566      // This ensures we evaluate every initializer only once.
567      this.isShared = true;
568    }
569   
570    internal override RopeNode<T> GetContentNode()
571    {
572      lock (this) {
573        if (this.cachedResults == null) {
574          if (this.initializer == null)
575            throw new InvalidOperationException("Trying to load this node recursively; or: a previous call to a rope initializer failed.");
576          Func<Rope<T>> initializerCopy = this.initializer;
577          this.initializer = null;
578          Rope<T> resultRope = initializerCopy();
579          if (resultRope == null)
580            throw new InvalidOperationException("Rope initializer returned null.");
581          RopeNode<T> resultNode = resultRope.root;
582          resultNode.Publish(); // result is shared between returned rope and the rope containing this function node
583          if (resultNode.length != this.length)
584            throw new InvalidOperationException("Rope initializer returned rope with incorrect length.");
585          if (resultNode.height == 0 && resultNode.contents == null) {
586            // ResultNode is another function node.
587            // We want to guarantee that GetContentNode() never returns function nodes, so we have to
588            // go down further in the tree.
589            this.cachedResults = resultNode.GetContentNode();
590          } else {
591            this.cachedResults = resultNode;
592          }
593        }
594        return this.cachedResults;
595      }
596    }
597   
598    #if DEBUG
599    internal override void AppendTreeToString(StringBuilder b, int indent)
600    {
601      RopeNode<T> resultNode;
602      lock (this) {
603        b.AppendLine(ToString());
604        resultNode = cachedResults;
605      }
606      indent += 2;
607      if (resultNode != null) {
608        b.Append(' ', indent);
609        b.Append("C: ");
610        resultNode.AppendTreeToString(b, indent);
611      }
612    }
613   
614    public override string ToString()
615    {
616      return "[FunctionNode length=" + length + " initializerRan=" + (initializer == null) + "]";
617    }
618    #endif
619  }
620}
Note: See TracBrowser for help on using the repository browser.