PageRenderTime 156ms CodeModel.GetById 20ms app.highlight 89ms RepoModel.GetById 21ms app.codeStats 1ms

/AvalonEdit/ICSharpCode.AvalonEdit/Utils/RopeNode.cs

http://github.com/icsharpcode/ILSpy
C# | 620 lines | 455 code | 53 blank | 112 comment | 141 complexity | aa07f4173287c9f43bda01bccee577bc MD5 | raw file
  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}