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 | |
---|
19 | using System; |
---|
20 | using System.Linq; |
---|
21 | using System.Collections.Generic; |
---|
22 | using System.Diagnostics; |
---|
23 | using System.Globalization; |
---|
24 | using System.Runtime.Serialization; |
---|
25 | using System.Text; |
---|
26 | |
---|
27 | namespace ICSharpCode.AvalonEdit.Utils |
---|
28 | { |
---|
29 | /// <summary> |
---|
30 | /// A kind of List<T>, but more efficient for random insertions/removal. |
---|
31 | /// Also has cheap Clone() and SubRope() implementations. |
---|
32 | /// </summary> |
---|
33 | /// <remarks> |
---|
34 | /// This class is not thread-safe: multiple concurrent write operations or writes concurrent to reads have undefined behaviour. |
---|
35 | /// Concurrent reads, however, are safe. |
---|
36 | /// However, clones of a rope are safe to use on other threads even though they share data with the original rope. |
---|
37 | /// </remarks> |
---|
38 | [Serializable] |
---|
39 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] |
---|
40 | public sealed class Rope<T> : IList<T>, ICloneable |
---|
41 | { |
---|
42 | internal RopeNode<T> root; |
---|
43 | |
---|
44 | internal Rope(RopeNode<T> root) |
---|
45 | { |
---|
46 | this.root = root; |
---|
47 | root.CheckInvariants(); |
---|
48 | } |
---|
49 | |
---|
50 | /// <summary> |
---|
51 | /// Creates a new rope representing the empty string. |
---|
52 | /// </summary> |
---|
53 | public Rope() |
---|
54 | { |
---|
55 | // we'll construct the empty rope as a clone of an imaginary static empty rope |
---|
56 | this.root = RopeNode<T>.emptyRopeNode; |
---|
57 | root.CheckInvariants(); |
---|
58 | } |
---|
59 | |
---|
60 | /// <summary> |
---|
61 | /// Creates a rope from the specified input. |
---|
62 | /// This operation runs in O(N). |
---|
63 | /// </summary> |
---|
64 | /// <exception cref="ArgumentNullException">input is null.</exception> |
---|
65 | public Rope(IEnumerable<T> input) |
---|
66 | { |
---|
67 | if (input == null) |
---|
68 | throw new ArgumentNullException("input"); |
---|
69 | Rope<T> inputRope = input as Rope<T>; |
---|
70 | if (inputRope != null) { |
---|
71 | // clone ropes instead of copying them |
---|
72 | inputRope.root.Publish(); |
---|
73 | this.root = inputRope.root; |
---|
74 | } else { |
---|
75 | string text = input as string; |
---|
76 | if (text != null) { |
---|
77 | // if a string is IEnumerable<T>, then T must be char |
---|
78 | ((Rope<char>)(object)this).root = CharRope.InitFromString(text); |
---|
79 | } else { |
---|
80 | T[] arr = ToArray(input); |
---|
81 | this.root = RopeNode<T>.CreateFromArray(arr, 0, arr.Length); |
---|
82 | } |
---|
83 | } |
---|
84 | this.root.CheckInvariants(); |
---|
85 | } |
---|
86 | |
---|
87 | /// <summary> |
---|
88 | /// Creates a rope from a part of the array. |
---|
89 | /// This operation runs in O(N). |
---|
90 | /// </summary> |
---|
91 | /// <exception cref="ArgumentNullException">input is null.</exception> |
---|
92 | public Rope(T[] array, int arrayIndex, int count) |
---|
93 | { |
---|
94 | VerifyArrayWithRange(array, arrayIndex, count); |
---|
95 | this.root = RopeNode<T>.CreateFromArray(array, arrayIndex, count); |
---|
96 | this.root.CheckInvariants(); |
---|
97 | } |
---|
98 | |
---|
99 | /// <summary> |
---|
100 | /// Creates a new rope that lazily initalizes its content. |
---|
101 | /// </summary> |
---|
102 | /// <param name="length">The length of the rope that will be lazily loaded.</param> |
---|
103 | /// <param name="initializer"> |
---|
104 | /// The callback that provides the content for this rope. |
---|
105 | /// <paramref name="initializer"/> will be called exactly once when the content of this rope is first requested. |
---|
106 | /// It must return a rope with the specified length. |
---|
107 | /// Because the initializer function is not called when a rope is cloned, and such clones may be used on another threads, |
---|
108 | /// it is possible for the initializer callback to occur on any thread. |
---|
109 | /// </param> |
---|
110 | /// <remarks> |
---|
111 | /// Any modifications inside the rope will also cause the content to be initialized. |
---|
112 | /// However, insertions at the beginning and the end, as well as inserting this rope into another or |
---|
113 | /// using the <see cref="Concat(Rope{T},Rope{T})"/> method, allows constructions of larger ropes where parts are |
---|
114 | /// lazily loaded. |
---|
115 | /// However, even methods like Concat may sometimes cause the initializer function to be called, e.g. when |
---|
116 | /// two short ropes are concatenated. |
---|
117 | /// </remarks> |
---|
118 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")] |
---|
119 | public Rope(int length, Func<Rope<T>> initializer) |
---|
120 | { |
---|
121 | if (initializer == null) |
---|
122 | throw new ArgumentNullException("initializer"); |
---|
123 | if (length < 0) |
---|
124 | throw new ArgumentOutOfRangeException("length", length, "Length must not be negative"); |
---|
125 | if (length == 0) { |
---|
126 | this.root = RopeNode<T>.emptyRopeNode; |
---|
127 | } else { |
---|
128 | this.root = new FunctionNode<T>(length, initializer); |
---|
129 | } |
---|
130 | this.root.CheckInvariants(); |
---|
131 | } |
---|
132 | |
---|
133 | static T[] ToArray(IEnumerable<T> input) |
---|
134 | { |
---|
135 | T[] arr = input as T[]; |
---|
136 | return arr ?? input.ToArray(); |
---|
137 | } |
---|
138 | |
---|
139 | /// <summary> |
---|
140 | /// Clones the rope. |
---|
141 | /// This operation runs in linear time to the number of rope nodes touched since the last clone was created. |
---|
142 | /// If you count the per-node cost to the operation modifying the rope (doing this doesn't increase the complexity of the modification operations); |
---|
143 | /// the remainder of Clone() runs in O(1). |
---|
144 | /// </summary> |
---|
145 | /// <remarks> |
---|
146 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
147 | /// </remarks> |
---|
148 | public Rope<T> Clone() |
---|
149 | { |
---|
150 | // The Publish() call actually modifies this rope instance; but this modification is thread-safe |
---|
151 | // as long as the tree structure doesn't change during the operation. |
---|
152 | root.Publish(); |
---|
153 | return new Rope<T>(root); |
---|
154 | } |
---|
155 | |
---|
156 | object ICloneable.Clone() |
---|
157 | { |
---|
158 | return this.Clone(); |
---|
159 | } |
---|
160 | |
---|
161 | /// <summary> |
---|
162 | /// Resets the rope to an empty list. |
---|
163 | /// Runs in O(1). |
---|
164 | /// </summary> |
---|
165 | public void Clear() |
---|
166 | { |
---|
167 | root = RopeNode<T>.emptyRopeNode; |
---|
168 | OnChanged(); |
---|
169 | } |
---|
170 | |
---|
171 | /// <summary> |
---|
172 | /// Gets the length of the rope. |
---|
173 | /// Runs in O(1). |
---|
174 | /// </summary> |
---|
175 | /// <remarks> |
---|
176 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
177 | /// </remarks> |
---|
178 | public int Length { |
---|
179 | get { return root.length; } |
---|
180 | } |
---|
181 | |
---|
182 | /// <summary> |
---|
183 | /// Gets the length of the rope. |
---|
184 | /// Runs in O(1). |
---|
185 | /// </summary> |
---|
186 | /// <remarks> |
---|
187 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
188 | /// </remarks> |
---|
189 | public int Count { |
---|
190 | get { return root.length; } |
---|
191 | } |
---|
192 | |
---|
193 | /// <summary> |
---|
194 | /// Inserts another rope into this rope. |
---|
195 | /// Runs in O(lg N + lg M), plus a per-node cost as if <c>newElements.Clone()</c> was called. |
---|
196 | /// </summary> |
---|
197 | /// <exception cref="ArgumentNullException">newElements is null.</exception> |
---|
198 | /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception> |
---|
199 | public void InsertRange(int index, Rope<T> newElements) |
---|
200 | { |
---|
201 | if (index < 0 || index > this.Length) { |
---|
202 | throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture)); |
---|
203 | } |
---|
204 | if (newElements == null) |
---|
205 | throw new ArgumentNullException("newElements"); |
---|
206 | newElements.root.Publish(); |
---|
207 | root = root.Insert(index, newElements.root); |
---|
208 | OnChanged(); |
---|
209 | } |
---|
210 | |
---|
211 | /// <summary> |
---|
212 | /// Inserts new elemetns into this rope. |
---|
213 | /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. |
---|
214 | /// </summary> |
---|
215 | /// <exception cref="ArgumentNullException">newElements is null.</exception> |
---|
216 | /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception> |
---|
217 | public void InsertRange(int index, IEnumerable<T> newElements) |
---|
218 | { |
---|
219 | if (newElements == null) |
---|
220 | throw new ArgumentNullException("newElements"); |
---|
221 | Rope<T> newElementsRope = newElements as Rope<T>; |
---|
222 | if (newElementsRope != null) { |
---|
223 | InsertRange(index, newElementsRope); |
---|
224 | } else { |
---|
225 | T[] arr = ToArray(newElements); |
---|
226 | InsertRange(index, arr, 0, arr.Length); |
---|
227 | } |
---|
228 | } |
---|
229 | |
---|
230 | /// <summary> |
---|
231 | /// Inserts new elements into this rope. |
---|
232 | /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. |
---|
233 | /// </summary> |
---|
234 | /// <exception cref="ArgumentNullException">newElements is null.</exception> |
---|
235 | /// <exception cref="ArgumentOutOfRangeException">index or length is outside the valid range.</exception> |
---|
236 | public void InsertRange(int index, T[] array, int arrayIndex, int count) |
---|
237 | { |
---|
238 | if (index < 0 || index > this.Length) { |
---|
239 | throw new ArgumentOutOfRangeException("index", index, "0 <= index <= " + this.Length.ToString(CultureInfo.InvariantCulture)); |
---|
240 | } |
---|
241 | VerifyArrayWithRange(array, arrayIndex, count); |
---|
242 | if (count > 0) { |
---|
243 | root = root.Insert(index, array, arrayIndex, count); |
---|
244 | OnChanged(); |
---|
245 | } |
---|
246 | } |
---|
247 | |
---|
248 | /// <summary> |
---|
249 | /// Appends multiple elements to the end of this rope. |
---|
250 | /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. |
---|
251 | /// </summary> |
---|
252 | /// <exception cref="ArgumentNullException">newElements is null.</exception> |
---|
253 | public void AddRange(IEnumerable<T> newElements) |
---|
254 | { |
---|
255 | InsertRange(this.Length, newElements); |
---|
256 | } |
---|
257 | |
---|
258 | /// <summary> |
---|
259 | /// Appends another rope to the end of this rope. |
---|
260 | /// Runs in O(lg N + lg M), plus a per-node cost as if <c>newElements.Clone()</c> was called. |
---|
261 | /// </summary> |
---|
262 | /// <exception cref="ArgumentNullException">newElements is null.</exception> |
---|
263 | public void AddRange(Rope<T> newElements) |
---|
264 | { |
---|
265 | InsertRange(this.Length, newElements); |
---|
266 | } |
---|
267 | |
---|
268 | /// <summary> |
---|
269 | /// Appends new elements to the end of this rope. |
---|
270 | /// Runs in O(lg N + M), where N is the length of this rope and M is the number of new elements. |
---|
271 | /// </summary> |
---|
272 | /// <exception cref="ArgumentNullException">array is null.</exception> |
---|
273 | public void AddRange(T[] array, int arrayIndex, int count) |
---|
274 | { |
---|
275 | InsertRange(this.Length, array, arrayIndex, count); |
---|
276 | } |
---|
277 | |
---|
278 | /// <summary> |
---|
279 | /// Removes a range of elements from the rope. |
---|
280 | /// Runs in O(lg N). |
---|
281 | /// </summary> |
---|
282 | /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> |
---|
283 | public void RemoveRange(int index, int count) |
---|
284 | { |
---|
285 | VerifyRange(index, count); |
---|
286 | if (count > 0) { |
---|
287 | root = root.RemoveRange(index, count); |
---|
288 | OnChanged(); |
---|
289 | } |
---|
290 | } |
---|
291 | |
---|
292 | /// <summary> |
---|
293 | /// Copies a range of the specified array into the rope, overwriting existing elements. |
---|
294 | /// Runs in O(lg N + M). |
---|
295 | /// </summary> |
---|
296 | public void SetRange(int index, T[] array, int arrayIndex, int count) |
---|
297 | { |
---|
298 | VerifyRange(index, count); |
---|
299 | VerifyArrayWithRange(array, arrayIndex, count); |
---|
300 | if (count > 0) { |
---|
301 | root = root.StoreElements(index, array, arrayIndex, count); |
---|
302 | OnChanged(); |
---|
303 | } |
---|
304 | } |
---|
305 | |
---|
306 | /// <summary> |
---|
307 | /// Creates a new rope and initializes it with a part of this rope. |
---|
308 | /// Runs in O(lg N) plus a per-node cost as if <c>this.Clone()</c> was called. |
---|
309 | /// </summary> |
---|
310 | /// <exception cref="ArgumentOutOfRangeException">offset or length is outside the valid range.</exception> |
---|
311 | /// <remarks> |
---|
312 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
313 | /// </remarks> |
---|
314 | public Rope<T> GetRange(int index, int count) |
---|
315 | { |
---|
316 | VerifyRange(index, count); |
---|
317 | Rope<T> newRope = Clone(); |
---|
318 | int endIndex = index + count; |
---|
319 | newRope.RemoveRange(endIndex, newRope.Length - endIndex); |
---|
320 | newRope.RemoveRange(0, index); |
---|
321 | return newRope; |
---|
322 | } |
---|
323 | |
---|
324 | /* |
---|
325 | #region Equality |
---|
326 | /// <summary> |
---|
327 | /// Gets whether the two ropes have the same content. |
---|
328 | /// Runs in O(N + M). |
---|
329 | /// </summary> |
---|
330 | /// <remarks> |
---|
331 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
332 | /// </remarks> |
---|
333 | public bool Equals(Rope other) |
---|
334 | { |
---|
335 | if (other == null) |
---|
336 | return false; |
---|
337 | // quick detection for ropes that are clones of each other: |
---|
338 | if (other.root == this.root) |
---|
339 | return true; |
---|
340 | if (other.Length != this.Length) |
---|
341 | return false; |
---|
342 | using (RopeTextReader a = new RopeTextReader(this, false)) { |
---|
343 | using (RopeTextReader b = new RopeTextReader(other, false)) { |
---|
344 | int charA, charB; |
---|
345 | do { |
---|
346 | charA = a.Read(); |
---|
347 | charB = b.Read(); |
---|
348 | if (charA != charB) |
---|
349 | return false; |
---|
350 | } while (charA != -1); |
---|
351 | return true; |
---|
352 | } |
---|
353 | } |
---|
354 | } |
---|
355 | |
---|
356 | /// <summary> |
---|
357 | /// Gets whether two ropes have the same content. |
---|
358 | /// Runs in O(N + M). |
---|
359 | /// </summary> |
---|
360 | public override bool Equals(object obj) |
---|
361 | { |
---|
362 | return Equals(obj as Rope); |
---|
363 | } |
---|
364 | |
---|
365 | /// <summary> |
---|
366 | /// Calculates the hash code of the rope's content. |
---|
367 | /// Runs in O(N). |
---|
368 | /// </summary> |
---|
369 | /// <remarks> |
---|
370 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
371 | /// </remarks> |
---|
372 | public override int GetHashCode() |
---|
373 | { |
---|
374 | int hashcode = 0; |
---|
375 | using (RopeTextReader reader = new RopeTextReader(this, false)) { |
---|
376 | unchecked { |
---|
377 | int val; |
---|
378 | while ((val = reader.Read()) != -1) { |
---|
379 | hashcode = hashcode * 31 + val; |
---|
380 | } |
---|
381 | } |
---|
382 | } |
---|
383 | return hashcode; |
---|
384 | } |
---|
385 | #endregion |
---|
386 | */ |
---|
387 | |
---|
388 | /// <summary> |
---|
389 | /// Concatenates two ropes. The input ropes are not modified. |
---|
390 | /// Runs in O(lg N + lg M). |
---|
391 | /// </summary> |
---|
392 | /// <remarks> |
---|
393 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
394 | /// </remarks> |
---|
395 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] |
---|
396 | public static Rope<T> Concat(Rope<T> left, Rope<T> right) |
---|
397 | { |
---|
398 | if (left == null) |
---|
399 | throw new ArgumentNullException("left"); |
---|
400 | if (right == null) |
---|
401 | throw new ArgumentNullException("right"); |
---|
402 | left.root.Publish(); |
---|
403 | right.root.Publish(); |
---|
404 | return new Rope<T>(RopeNode<T>.Concat(left.root, right.root)); |
---|
405 | } |
---|
406 | |
---|
407 | /// <summary> |
---|
408 | /// Concatenates multiple ropes. The input ropes are not modified. |
---|
409 | /// </summary> |
---|
410 | /// <remarks> |
---|
411 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
412 | /// </remarks> |
---|
413 | [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")] |
---|
414 | public static Rope<T> Concat(params Rope<T>[] ropes) |
---|
415 | { |
---|
416 | if (ropes == null) |
---|
417 | throw new ArgumentNullException("ropes"); |
---|
418 | Rope<T> result = new Rope<T>(); |
---|
419 | foreach (Rope<T> r in ropes) |
---|
420 | result.AddRange(r); |
---|
421 | return result; |
---|
422 | } |
---|
423 | |
---|
424 | #region Caches / Changed event |
---|
425 | internal struct RopeCacheEntry |
---|
426 | { |
---|
427 | internal readonly RopeNode<T> node; |
---|
428 | internal readonly int nodeStartIndex; |
---|
429 | |
---|
430 | internal RopeCacheEntry(RopeNode<T> node, int nodeStartOffset) |
---|
431 | { |
---|
432 | this.node = node; |
---|
433 | this.nodeStartIndex = nodeStartOffset; |
---|
434 | } |
---|
435 | |
---|
436 | internal bool IsInside(int offset) |
---|
437 | { |
---|
438 | return offset >= nodeStartIndex && offset < nodeStartIndex + node.length; |
---|
439 | } |
---|
440 | } |
---|
441 | |
---|
442 | // cached pointer to 'last used node', used to speed up accesses by index that are close together |
---|
443 | [NonSerialized] |
---|
444 | volatile ImmutableStack<RopeCacheEntry> lastUsedNodeStack; |
---|
445 | |
---|
446 | internal void OnChanged() |
---|
447 | { |
---|
448 | lastUsedNodeStack = null; |
---|
449 | |
---|
450 | root.CheckInvariants(); |
---|
451 | } |
---|
452 | #endregion |
---|
453 | |
---|
454 | #region GetChar / SetChar |
---|
455 | /// <summary> |
---|
456 | /// Gets/Sets a single character. |
---|
457 | /// Runs in O(lg N) for random access. Sequential read-only access benefits from a special optimization and runs in amortized O(1). |
---|
458 | /// </summary> |
---|
459 | /// <exception cref="ArgumentOutOfRangeException">Offset is outside the valid range (0 to Length-1).</exception> |
---|
460 | /// <remarks> |
---|
461 | /// The getter counts as a read access and may be called concurrently to other read accesses. |
---|
462 | /// </remarks> |
---|
463 | public T this[int index] { |
---|
464 | get { |
---|
465 | // use unsigned integers - this way negative values for index overflow and can be tested for with the same check |
---|
466 | if (unchecked((uint)index >= (uint)this.Length)) { |
---|
467 | throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); |
---|
468 | } |
---|
469 | RopeCacheEntry entry = FindNodeUsingCache(index).PeekOrDefault(); |
---|
470 | return entry.node.contents[index - entry.nodeStartIndex]; |
---|
471 | } |
---|
472 | set { |
---|
473 | if (index < 0 || index >= this.Length) { |
---|
474 | throw new ArgumentOutOfRangeException("index", index, "0 <= index < " + this.Length.ToString(CultureInfo.InvariantCulture)); |
---|
475 | } |
---|
476 | root = root.SetElement(index, value); |
---|
477 | OnChanged(); |
---|
478 | /* Here's a try at implementing the setter using the cached node stack (UNTESTED code!). |
---|
479 | * However I don't use the code because it's complicated and doesn't integrate correctly with change notifications. |
---|
480 | * Instead, I'll use the much easier to understand recursive solution. |
---|
481 | * Oh, and it also doesn't work correctly with function nodes. |
---|
482 | ImmutableStack<RopeCacheEntry> nodeStack = FindNodeUsingCache(offset); |
---|
483 | RopeCacheEntry entry = nodeStack.Peek(); |
---|
484 | if (!entry.node.isShared) { |
---|
485 | entry.node.contents[offset - entry.nodeStartOffset] = value; |
---|
486 | // missing: clear the caches except for the node stack cache (e.g. ToString() cache?) |
---|
487 | } else { |
---|
488 | RopeNode oldNode = entry.node; |
---|
489 | RopeNode newNode = oldNode.Clone(); |
---|
490 | newNode.contents[offset - entry.nodeStartOffset] = value; |
---|
491 | for (nodeStack = nodeStack.Pop(); !nodeStack.IsEmpty; nodeStack = nodeStack.Pop()) { |
---|
492 | RopeNode parentNode = nodeStack.Peek().node; |
---|
493 | RopeNode newParentNode = parentNode.CloneIfShared(); |
---|
494 | if (newParentNode.left == oldNode) { |
---|
495 | newParentNode.left = newNode; |
---|
496 | } else { |
---|
497 | Debug.Assert(newParentNode.right == oldNode); |
---|
498 | newParentNode.right = newNode; |
---|
499 | } |
---|
500 | if (parentNode == newParentNode) { |
---|
501 | // we were able to change the existing node (it was not shared); |
---|
502 | // there's no reason to go further upwards |
---|
503 | ClearCacheOnModification(); |
---|
504 | return; |
---|
505 | } else { |
---|
506 | oldNode = parentNode; |
---|
507 | newNode = newParentNode; |
---|
508 | } |
---|
509 | } |
---|
510 | // we reached the root of the rope. |
---|
511 | Debug.Assert(root == oldNode); |
---|
512 | root = newNode; |
---|
513 | ClearCacheOnModification(); |
---|
514 | }*/ |
---|
515 | } |
---|
516 | } |
---|
517 | |
---|
518 | internal ImmutableStack<RopeCacheEntry> FindNodeUsingCache(int index) |
---|
519 | { |
---|
520 | Debug.Assert(index >= 0 && index < this.Length); |
---|
521 | |
---|
522 | // thread safety: fetch stack into local variable |
---|
523 | ImmutableStack<RopeCacheEntry> stack = lastUsedNodeStack; |
---|
524 | ImmutableStack<RopeCacheEntry> oldStack = stack; |
---|
525 | |
---|
526 | if (stack == null) { |
---|
527 | stack = ImmutableStack<RopeCacheEntry>.Empty.Push(new RopeCacheEntry(root, 0)); |
---|
528 | } |
---|
529 | while (!stack.PeekOrDefault().IsInside(index)) |
---|
530 | stack = stack.Pop(); |
---|
531 | while (true) { |
---|
532 | RopeCacheEntry entry = stack.PeekOrDefault(); |
---|
533 | // check if we've reached a leaf or function node |
---|
534 | if (entry.node.height == 0) { |
---|
535 | if (entry.node.contents == null) { |
---|
536 | // this is a function node - go down into its subtree |
---|
537 | entry = new RopeCacheEntry(entry.node.GetContentNode(), entry.nodeStartIndex); |
---|
538 | // entry is now guaranteed NOT to be another function node |
---|
539 | } |
---|
540 | if (entry.node.contents != null) { |
---|
541 | // this is a node containing actual content, so we're done |
---|
542 | break; |
---|
543 | } |
---|
544 | } |
---|
545 | // go down towards leaves |
---|
546 | if (index - entry.nodeStartIndex >= entry.node.left.length) |
---|
547 | stack = stack.Push(new RopeCacheEntry(entry.node.right, entry.nodeStartIndex + entry.node.left.length)); |
---|
548 | else |
---|
549 | stack = stack.Push(new RopeCacheEntry(entry.node.left, entry.nodeStartIndex)); |
---|
550 | } |
---|
551 | |
---|
552 | // write back stack to volatile cache variable |
---|
553 | // (in multithreaded access, it doesn't matter which of the threads wins - it's just a cache) |
---|
554 | if (oldStack != stack) { |
---|
555 | // no need to write when we the cache variable didn't change |
---|
556 | lastUsedNodeStack = stack; |
---|
557 | } |
---|
558 | |
---|
559 | // this method guarantees that it finds a leaf node |
---|
560 | Debug.Assert(stack.Peek().node.contents != null); |
---|
561 | return stack; |
---|
562 | } |
---|
563 | #endregion |
---|
564 | |
---|
565 | #region ToString / WriteTo |
---|
566 | internal void VerifyRange(int startIndex, int length) |
---|
567 | { |
---|
568 | if (startIndex < 0 || startIndex > this.Length) { |
---|
569 | throw new ArgumentOutOfRangeException("startIndex", startIndex, "0 <= startIndex <= " + this.Length.ToString(CultureInfo.InvariantCulture)); |
---|
570 | } |
---|
571 | if (length < 0 || startIndex + length > this.Length) { |
---|
572 | throw new ArgumentOutOfRangeException("length", length, "0 <= length, startIndex(" + startIndex + ")+length <= " + this.Length.ToString(CultureInfo.InvariantCulture)); |
---|
573 | } |
---|
574 | } |
---|
575 | |
---|
576 | internal static void VerifyArrayWithRange(T[] array, int arrayIndex, int count) |
---|
577 | { |
---|
578 | if (array == null) |
---|
579 | throw new ArgumentNullException("array"); |
---|
580 | if (arrayIndex < 0 || arrayIndex > array.Length) { |
---|
581 | throw new ArgumentOutOfRangeException("startIndex", arrayIndex, "0 <= arrayIndex <= " + array.Length.ToString(CultureInfo.InvariantCulture)); |
---|
582 | } |
---|
583 | if (count < 0 || arrayIndex + count > array.Length) { |
---|
584 | throw new ArgumentOutOfRangeException("count", count, "0 <= length, arrayIndex(" + arrayIndex + ")+count <= " + array.Length.ToString(CultureInfo.InvariantCulture)); |
---|
585 | } |
---|
586 | } |
---|
587 | |
---|
588 | /// <summary> |
---|
589 | /// Creates a string from the rope. Runs in O(N). |
---|
590 | /// </summary> |
---|
591 | /// <returns>A string consisting of all elements in the rope as comma-separated list in {}. |
---|
592 | /// As a special case, Rope<char> will return its contents as string without any additional separators or braces, |
---|
593 | /// so it can be used like StringBuilder.ToString().</returns> |
---|
594 | /// <remarks> |
---|
595 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
596 | /// </remarks> |
---|
597 | public override string ToString() |
---|
598 | { |
---|
599 | Rope<char> charRope = this as Rope<char>; |
---|
600 | if (charRope != null) { |
---|
601 | return charRope.ToString(0, this.Length); |
---|
602 | } else { |
---|
603 | StringBuilder b = new StringBuilder(); |
---|
604 | foreach (T element in this) { |
---|
605 | if (b.Length == 0) |
---|
606 | b.Append('{'); |
---|
607 | else |
---|
608 | b.Append(", "); |
---|
609 | b.Append(element.ToString()); |
---|
610 | } |
---|
611 | b.Append('}'); |
---|
612 | return b.ToString(); |
---|
613 | } |
---|
614 | } |
---|
615 | |
---|
616 | internal string GetTreeAsString() |
---|
617 | { |
---|
618 | #if DEBUG |
---|
619 | return root.GetTreeAsString(); |
---|
620 | #else |
---|
621 | return "Not available in release build."; |
---|
622 | #endif |
---|
623 | } |
---|
624 | #endregion |
---|
625 | |
---|
626 | bool ICollection<T>.IsReadOnly { |
---|
627 | get { return false; } |
---|
628 | } |
---|
629 | |
---|
630 | /// <summary> |
---|
631 | /// Finds the first occurance of item. |
---|
632 | /// Runs in O(N). |
---|
633 | /// </summary> |
---|
634 | /// <returns>The index of the first occurance of item, or -1 if it cannot be found.</returns> |
---|
635 | /// <remarks> |
---|
636 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
637 | /// </remarks> |
---|
638 | public int IndexOf(T item) |
---|
639 | { |
---|
640 | return IndexOf(item, 0, this.Length); |
---|
641 | } |
---|
642 | |
---|
643 | /// <summary> |
---|
644 | /// Gets the index of the first occurrence the specified item. |
---|
645 | /// </summary> |
---|
646 | /// <param name="item">Item to search for.</param> |
---|
647 | /// <param name="startIndex">Start index of the search.</param> |
---|
648 | /// <param name="count">Length of the area to search.</param> |
---|
649 | /// <returns>The first index where the item was found; or -1 if no occurrence was found.</returns> |
---|
650 | /// <remarks> |
---|
651 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
652 | /// </remarks> |
---|
653 | public int IndexOf(T item, int startIndex, int count) |
---|
654 | { |
---|
655 | VerifyRange(startIndex, count); |
---|
656 | |
---|
657 | while (count > 0) { |
---|
658 | var entry = FindNodeUsingCache(startIndex).PeekOrDefault(); |
---|
659 | T[] contents = entry.node.contents; |
---|
660 | int startWithinNode = startIndex - entry.nodeStartIndex; |
---|
661 | int nodeLength = Math.Min(entry.node.length, startWithinNode + count); |
---|
662 | int r = Array.IndexOf(contents, item, startWithinNode, nodeLength - startWithinNode); |
---|
663 | if (r >= 0) |
---|
664 | return entry.nodeStartIndex + r; |
---|
665 | count -= nodeLength - startWithinNode; |
---|
666 | startIndex = entry.nodeStartIndex + nodeLength; |
---|
667 | } |
---|
668 | return -1; |
---|
669 | } |
---|
670 | |
---|
671 | /// <summary> |
---|
672 | /// Gets the index of the last occurrence of the specified item in this rope. |
---|
673 | /// </summary> |
---|
674 | public int LastIndexOf(T item) |
---|
675 | { |
---|
676 | return LastIndexOf(item, 0, this.Length); |
---|
677 | } |
---|
678 | |
---|
679 | /// <summary> |
---|
680 | /// Gets the index of the last occurrence of the specified item in this rope. |
---|
681 | /// </summary> |
---|
682 | /// <param name="item">The search item</param> |
---|
683 | /// <param name="startIndex">Start index of the area to search.</param> |
---|
684 | /// <param name="count">Length of the area to search.</param> |
---|
685 | /// <returns>The last index where the item was found; or -1 if no occurrence was found.</returns> |
---|
686 | /// <remarks>The search proceeds backwards from (startIndex+count) to startIndex. |
---|
687 | /// This is different than the meaning of the parameters on Array.LastIndexOf!</remarks> |
---|
688 | public int LastIndexOf(T item, int startIndex, int count) |
---|
689 | { |
---|
690 | VerifyRange(startIndex, count); |
---|
691 | |
---|
692 | var comparer = EqualityComparer<T>.Default; |
---|
693 | for (int i = startIndex + count - 1; i >= startIndex; i--) { |
---|
694 | if (comparer.Equals(this[i], item)) |
---|
695 | return i; |
---|
696 | } |
---|
697 | return -1; |
---|
698 | } |
---|
699 | |
---|
700 | /// <summary> |
---|
701 | /// Inserts the item at the specified index in the rope. |
---|
702 | /// Runs in O(lg N). |
---|
703 | /// </summary> |
---|
704 | public void Insert(int index, T item) |
---|
705 | { |
---|
706 | InsertRange(index, new[] { item }, 0, 1); |
---|
707 | } |
---|
708 | |
---|
709 | /// <summary> |
---|
710 | /// Removes a single item from the rope. |
---|
711 | /// Runs in O(lg N). |
---|
712 | /// </summary> |
---|
713 | public void RemoveAt(int index) |
---|
714 | { |
---|
715 | RemoveRange(index, 1); |
---|
716 | } |
---|
717 | |
---|
718 | /// <summary> |
---|
719 | /// Appends the item at the end of the rope. |
---|
720 | /// Runs in O(lg N). |
---|
721 | /// </summary> |
---|
722 | public void Add(T item) |
---|
723 | { |
---|
724 | InsertRange(this.Length, new[] { item }, 0, 1); |
---|
725 | } |
---|
726 | |
---|
727 | /// <summary> |
---|
728 | /// Searches the item in the rope. |
---|
729 | /// Runs in O(N). |
---|
730 | /// </summary> |
---|
731 | /// <remarks> |
---|
732 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
733 | /// </remarks> |
---|
734 | public bool Contains(T item) |
---|
735 | { |
---|
736 | return IndexOf(item) >= 0; |
---|
737 | } |
---|
738 | |
---|
739 | /// <summary> |
---|
740 | /// Copies the whole content of the rope into the specified array. |
---|
741 | /// Runs in O(N). |
---|
742 | /// </summary> |
---|
743 | /// <remarks> |
---|
744 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
745 | /// </remarks> |
---|
746 | public void CopyTo(T[] array, int arrayIndex) |
---|
747 | { |
---|
748 | CopyTo(0, array, arrayIndex, this.Length); |
---|
749 | } |
---|
750 | |
---|
751 | /// <summary> |
---|
752 | /// Copies the a part of the rope into the specified array. |
---|
753 | /// Runs in O(lg N + M). |
---|
754 | /// </summary> |
---|
755 | /// <remarks> |
---|
756 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
757 | /// </remarks> |
---|
758 | public void CopyTo(int index, T[] array, int arrayIndex, int count) |
---|
759 | { |
---|
760 | VerifyRange(index, count); |
---|
761 | VerifyArrayWithRange(array, arrayIndex, count); |
---|
762 | this.root.CopyTo(index, array, arrayIndex, count); |
---|
763 | } |
---|
764 | |
---|
765 | /// <summary> |
---|
766 | /// Removes the first occurance of an item from the rope. |
---|
767 | /// Runs in O(N). |
---|
768 | /// </summary> |
---|
769 | public bool Remove(T item) |
---|
770 | { |
---|
771 | int index = IndexOf(item); |
---|
772 | if (index >= 0) { |
---|
773 | RemoveAt(index); |
---|
774 | return true; |
---|
775 | } |
---|
776 | return false; |
---|
777 | } |
---|
778 | |
---|
779 | /// <summary> |
---|
780 | /// Retrieves an enumerator to iterate through the rope. |
---|
781 | /// The enumerator will reflect the state of the rope from the GetEnumerator() call, further modifications |
---|
782 | /// to the rope will not be visible to the enumerator. |
---|
783 | /// </summary> |
---|
784 | /// <remarks> |
---|
785 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
786 | /// </remarks> |
---|
787 | public IEnumerator<T> GetEnumerator() |
---|
788 | { |
---|
789 | this.root.Publish(); |
---|
790 | return Enumerate(root); |
---|
791 | } |
---|
792 | |
---|
793 | /// <summary> |
---|
794 | /// Creates an array and copies the contents of the rope into it. |
---|
795 | /// Runs in O(N). |
---|
796 | /// </summary> |
---|
797 | /// <remarks> |
---|
798 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
799 | /// </remarks> |
---|
800 | public T[] ToArray() |
---|
801 | { |
---|
802 | T[] arr = new T[this.Length]; |
---|
803 | this.root.CopyTo(0, arr, 0, arr.Length); |
---|
804 | return arr; |
---|
805 | } |
---|
806 | |
---|
807 | /// <summary> |
---|
808 | /// Creates an array and copies the contents of the rope into it. |
---|
809 | /// Runs in O(N). |
---|
810 | /// </summary> |
---|
811 | /// <remarks> |
---|
812 | /// This method counts as a read access and may be called concurrently to other read accesses. |
---|
813 | /// </remarks> |
---|
814 | public T[] ToArray(int startIndex, int count) |
---|
815 | { |
---|
816 | VerifyRange(startIndex, count); |
---|
817 | T[] arr = new T[count]; |
---|
818 | CopyTo(startIndex, arr, 0, count); |
---|
819 | return arr; |
---|
820 | } |
---|
821 | |
---|
822 | static IEnumerator<T> Enumerate(RopeNode<T> node) |
---|
823 | { |
---|
824 | Stack<RopeNode<T>> stack = new Stack<RopeNode<T>>(); |
---|
825 | while (node != null) { |
---|
826 | // go to leftmost node, pushing the right parts that we'll have to visit later |
---|
827 | while (node.contents == null) { |
---|
828 | if (node.height == 0) { |
---|
829 | // go down into function nodes |
---|
830 | node = node.GetContentNode(); |
---|
831 | continue; |
---|
832 | } |
---|
833 | Debug.Assert(node.right != null); |
---|
834 | stack.Push(node.right); |
---|
835 | node = node.left; |
---|
836 | } |
---|
837 | // yield contents of leaf node |
---|
838 | for (int i = 0; i < node.length; i++) { |
---|
839 | yield return node.contents[i]; |
---|
840 | } |
---|
841 | // go up to the next node not visited yet |
---|
842 | if (stack.Count > 0) |
---|
843 | node = stack.Pop(); |
---|
844 | else |
---|
845 | node = null; |
---|
846 | } |
---|
847 | } |
---|
848 | |
---|
849 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() |
---|
850 | { |
---|
851 | return this.GetEnumerator(); |
---|
852 | } |
---|
853 | } |
---|
854 | } |
---|