Mercurial > projects > dwt-addons
comparison dwtx/text/edits/TextEdit.d @ 129:eb30df5ca28b
Added JFace Text sources
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sat, 23 Aug 2008 19:10:48 +0200 |
parents | |
children | b56e9be9fe88 |
comparison
equal
deleted
inserted
replaced
128:8df1d4193877 | 129:eb30df5ca28b |
---|---|
1 /******************************************************************************* | |
2 * Copyright (c) 2000, 2008 IBM Corporation and others. | |
3 * All rights reserved. This program and the accompanying materials | |
4 * are made available under the terms of the Eclipse Public License v1.0 | |
5 * which accompanies this distribution, and is available at | |
6 * http://www.eclipse.org/legal/epl-v10.html | |
7 * | |
8 * Contributors: | |
9 * IBM Corporation - initial API and implementation | |
10 * Port to the D programming language: | |
11 * Frank Benoit <benoit@tionex.de> | |
12 *******************************************************************************/ | |
13 module dwtx.text.edits.TextEdit; | |
14 | |
15 import dwt.dwthelper.utils; | |
16 | |
17 import java.util.ArrayList; | |
18 import java.util.Collections; | |
19 import java.util.Comparator; | |
20 import java.util.Iterator; | |
21 import java.util.List; | |
22 | |
23 import dwtx.core.runtime.Assert; | |
24 import dwtx.jface.text.BadLocationException; | |
25 import dwtx.jface.text.IDocument; | |
26 import dwtx.jface.text.IRegion; | |
27 import dwtx.jface.text.Region; | |
28 | |
29 | |
30 /** | |
31 * A text edit describes an elementary text manipulation operation. Edits are | |
32 * executed by applying them to a document (e.g. an instance of <code>IDocument | |
33 * </code>). | |
34 * <p> | |
35 * Text edits form a tree. Clients can navigate the tree upwards, from child to | |
36 * parent, as well as downwards. Newly created edits are un-parented. New edits | |
37 * are added to the tree by calling one of the <code>add</code> methods on a parent | |
38 * edit. | |
39 * </p> | |
40 * <p> | |
41 * An edit tree is well formed in the following sense: | |
42 * <ul> | |
43 * <li>a parent edit covers all its children</li> | |
44 * <li>children don't overlap</li> | |
45 * <li>an edit with length 0 can't have any children</li> | |
46 * </ul> | |
47 * Any manipulation of the tree that violates one of the above requirements results | |
48 * in a <code>MalformedTreeException</code>. | |
49 * </p> | |
50 * <p> | |
51 * Insert edits are represented by an edit of length 0. If more than one insert | |
52 * edit exists at the same offset then the edits are executed in the order in which | |
53 * they have been added to a parent. The following code example: | |
54 * <pre> | |
55 * IDocument document= new Document("org"); | |
56 * MultiTextEdit edit= new MultiTextEdit(); | |
57 * edit.addChild(new InsertEdit(0, "www.")); | |
58 * edit.addChild(new InsertEdit(0, "eclipse.")); | |
59 * edit.apply(document); | |
60 * </pre> | |
61 * therefore results in string: "www.eclipse.org". | |
62 * </p> | |
63 * <p> | |
64 * Text edits can be executed in a mode where the edit's region is updated to | |
65 * reflect the edit's position in the changed document. Region updating is enabled | |
66 * by default or can be requested by passing <code>UPDATE_REGIONS</code> to the | |
67 * {@link #apply(IDocument, int) apply(IDocument, int)} method. In the above example | |
68 * the region of the <code>InsertEdit(0, "eclipse.")</code> edit after executing | |
69 * the root edit is <code>[3, 8]</code>. If the region of an edit got deleted during | |
70 * change execution the region is set to <code>[-1, -1]</code> and the method {@link | |
71 * #isDeleted() isDeleted} returns <code>true</code>. | |
72 * </p> | |
73 * This class isn't intended to be subclassed outside of the edit framework. Clients | |
74 * are only allowed to subclass <code>MultiTextEdit</code>. | |
75 * | |
76 * @since 3.0 | |
77 * @noextend This class is not intended to be subclassed by clients. | |
78 */ | |
79 public abstract class TextEdit { | |
80 | |
81 /** | |
82 * Flags indicating that neither <code>CREATE_UNDO</code> nor | |
83 * <code>UPDATE_REGIONS</code> is set. | |
84 */ | |
85 public static final int NONE= 0; | |
86 | |
87 /** | |
88 * Flags indicating that applying an edit tree to a document | |
89 * is supposed to create a corresponding undo edit. If not | |
90 * specified <code>null</code> is returned from method <code> | |
91 * apply</code>. | |
92 */ | |
93 public static final int CREATE_UNDO= 1 << 0; | |
94 | |
95 /** | |
96 * Flag indicating that the edit's region will be updated to | |
97 * reflect its position in the changed document. If not specified | |
98 * when applying an edit tree to a document the edit's region will | |
99 * be arbitrary. It is even not guaranteed that the tree is still | |
100 * well formed. | |
101 */ | |
102 public static final int UPDATE_REGIONS= 1 << 1; | |
103 | |
104 private static class InsertionComparator : Comparator { | |
105 public int compare(Object o1, Object o2) throws MalformedTreeException { | |
106 TextEdit edit1= (TextEdit)o1; | |
107 TextEdit edit2= (TextEdit)o2; | |
108 | |
109 int offset1= edit1.getOffset(); | |
110 int length1= edit1.getLength(); | |
111 | |
112 int offset2= edit2.getOffset(); | |
113 int length2= edit2.getLength(); | |
114 | |
115 if (offset1 is offset2 && length1 is 0 && length2 is 0) { | |
116 return 0; | |
117 } | |
118 if (offset1 + length1 <= offset2) { | |
119 return -1; | |
120 } | |
121 if (offset2 + length2 <= offset1) { | |
122 return 1; | |
123 } | |
124 throw new MalformedTreeException( | |
125 null, edit1, | |
126 TextEditMessages.getString("TextEdit.overlapping")); //$NON-NLS-1$ | |
127 } | |
128 } | |
129 | |
130 private static final TextEdit[] EMPTY_ARRAY= new TextEdit[0]; | |
131 private static final InsertionComparator INSERTION_COMPARATOR= new InsertionComparator(); | |
132 | |
133 private static final int DELETED_VALUE= -1; | |
134 | |
135 private int fOffset; | |
136 private int fLength; | |
137 | |
138 private TextEdit fParent; | |
139 private List fChildren; | |
140 | |
141 int fDelta; | |
142 | |
143 /** | |
144 * Create a new text edit. Parent is initialized to <code> | |
145 * null<code> and the edit doesn't have any children. | |
146 * | |
147 * @param offset the edit's offset | |
148 * @param length the edit's length | |
149 */ | |
150 protected TextEdit(int offset, int length) { | |
151 Assert.isTrue(offset >= 0 && length >= 0); | |
152 fOffset= offset; | |
153 fLength= length; | |
154 fDelta= 0; | |
155 } | |
156 | |
157 /** | |
158 * Copy constructor | |
159 * | |
160 * @param source the source to copy form | |
161 */ | |
162 protected TextEdit(TextEdit source) { | |
163 fOffset= source.fOffset; | |
164 fLength= source.fLength; | |
165 fDelta= 0; | |
166 } | |
167 | |
168 //---- Region management ----------------------------------------------- | |
169 | |
170 /** | |
171 * Returns the range that this edit is manipulating. The returned | |
172 * <code>IRegion</code> contains the edit's offset and length at | |
173 * the point in time when this call is made. Any subsequent changes | |
174 * to the edit's offset and length aren't reflected in the returned | |
175 * region object. | |
176 * <p> | |
177 * Creating a region for a deleted edit will result in an assertion | |
178 * failure. | |
179 * | |
180 * @return the manipulated region | |
181 */ | |
182 public final IRegion getRegion() { | |
183 return new Region(getOffset(), getLength()); | |
184 } | |
185 | |
186 /** | |
187 * Returns the offset of the edit. An offset is a 0-based | |
188 * character index. Returns <code>-1</code> if the edit | |
189 * is marked as deleted. | |
190 * | |
191 * @return the offset of the edit | |
192 */ | |
193 public int getOffset() { | |
194 return fOffset; | |
195 } | |
196 | |
197 /** | |
198 * Returns the length of the edit. Returns <code>-1</code> | |
199 * if the edit is marked as deleted. | |
200 * | |
201 * @return the length of the edit | |
202 */ | |
203 public int getLength() { | |
204 return fLength; | |
205 } | |
206 | |
207 /** | |
208 * Returns the inclusive end position of this edit. The inclusive end | |
209 * position denotes the last character of the region manipulated by | |
210 * this edit. The returned value is the result of the following | |
211 * calculation: | |
212 * <pre> | |
213 * getOffset() + getLength() - 1; | |
214 * <pre> | |
215 * | |
216 * @return the inclusive end position | |
217 */ | |
218 public final int getInclusiveEnd() { | |
219 return getOffset() + getLength() - 1; | |
220 } | |
221 | |
222 /** | |
223 * Returns the exclusive end position of this edit. The exclusive end | |
224 * position denotes the next character of the region manipulated by | |
225 * this edit. The returned value is the result of the following | |
226 * calculation: | |
227 * <pre> | |
228 * getOffset() + getLength(); | |
229 * </pre> | |
230 * | |
231 * @return the exclusive end position | |
232 */ | |
233 public final int getExclusiveEnd() { | |
234 return getOffset() + getLength(); | |
235 } | |
236 | |
237 /** | |
238 * Returns whether this edit has been deleted or not. | |
239 * | |
240 * @return <code>true</code> if the edit has been | |
241 * deleted; otherwise <code>false</code> is returned. | |
242 */ | |
243 public final bool isDeleted() { | |
244 return fOffset is DELETED_VALUE && fLength is DELETED_VALUE; | |
245 } | |
246 | |
247 /** | |
248 * Move all offsets in the tree by the given delta. This node must be a | |
249 * root node. The resulting offsets must be greater or equal to zero. | |
250 * | |
251 * @param delta the delta | |
252 * @since 3.1 | |
253 */ | |
254 public final void moveTree(int delta) { | |
255 Assert.isTrue(fParent is null); | |
256 Assert.isTrue(getOffset() + delta >= 0); | |
257 internalMoveTree(delta); | |
258 } | |
259 | |
260 /** | |
261 * Returns <code>true</code> if the edit covers the given edit | |
262 * <code>other</code>. It is up to the concrete text edit to | |
263 * decide if a edit of length zero can cover another edit. | |
264 * | |
265 * @param other the other edit | |
266 * @return <code>true<code> if the edit covers the other edit; | |
267 * otherwise <code>false</code> is returned. | |
268 */ | |
269 public bool covers(TextEdit other) { | |
270 if (getLength() is 0 && !canZeroLengthCover()) | |
271 return false; | |
272 | |
273 if (!other.isDefined()) | |
274 return true; | |
275 | |
276 int thisOffset= getOffset(); | |
277 int otherOffset= other.getOffset(); | |
278 return thisOffset <= otherOffset && otherOffset + other.getLength() <= thisOffset + getLength(); | |
279 } | |
280 | |
281 /** | |
282 * Returns <code>true</code> if an edit with length zero can cover | |
283 * another edit. Returns <code>false</code> otherwise. | |
284 * | |
285 * @return whether an edit of length zero can cover another edit | |
286 */ | |
287 protected bool canZeroLengthCover() { | |
288 return false; | |
289 } | |
290 | |
291 /** | |
292 * Returns whether the region of this edit is defined or not. | |
293 * | |
294 * @return whether the region of this edit is defined or not | |
295 * | |
296 * @since 3.1 | |
297 */ | |
298 bool isDefined() { | |
299 return true; | |
300 } | |
301 | |
302 //---- parent and children management ----------------------------- | |
303 | |
304 /** | |
305 * Returns the edit's parent. The method returns <code>null</code> | |
306 * if this edit hasn't been add to another edit. | |
307 * | |
308 * @return the edit's parent | |
309 */ | |
310 public final TextEdit getParent() { | |
311 return fParent; | |
312 } | |
313 | |
314 /** | |
315 * Returns the root edit of the edit tree. | |
316 * | |
317 * @return the root edit of the edit tree | |
318 * @since 3.1 | |
319 */ | |
320 public final TextEdit getRoot() { | |
321 TextEdit result= this; | |
322 while (result.fParent !is null) { | |
323 result= result.fParent; | |
324 } | |
325 return result; | |
326 } | |
327 | |
328 /** | |
329 * Adds the given edit <code>child</code> to this edit. | |
330 * | |
331 * @param child the child edit to add | |
332 * @exception MalformedTreeException is thrown if the child | |
333 * edit can't be added to this edit. This is the case if the child | |
334 * overlaps with one of its siblings or if the child edit's region | |
335 * isn't fully covered by this edit. | |
336 */ | |
337 public final void addChild(TextEdit child) throws MalformedTreeException { | |
338 internalAdd(child); | |
339 } | |
340 | |
341 /** | |
342 * Adds all edits in <code>edits</code> to this edit. | |
343 * | |
344 * @param edits the text edits to add | |
345 * @exception MalformedTreeException is thrown if one of | |
346 * the given edits can't be added to this edit. | |
347 * | |
348 * @see #addChild(TextEdit) | |
349 */ | |
350 public final void addChildren(TextEdit[] edits) throws MalformedTreeException { | |
351 for (int i= 0; i < edits.length; i++) { | |
352 internalAdd(edits[i]); | |
353 } | |
354 } | |
355 | |
356 /** | |
357 * Removes the edit specified by the given index from the list | |
358 * of children. Returns the child edit that was removed from | |
359 * the list of children. The parent of the returned edit is | |
360 * set to <code>null</code>. | |
361 * | |
362 * @param index the index of the edit to remove | |
363 * @return the removed edit | |
364 * @exception IndexOutOfBoundsException if the index | |
365 * is out of range | |
366 */ | |
367 public final TextEdit removeChild(int index) { | |
368 if (fChildren is null) | |
369 throw new IndexOutOfBoundsException("Index: " + index + " Size: 0"); //$NON-NLS-1$//$NON-NLS-2$ | |
370 TextEdit result= (TextEdit)fChildren.remove(index); | |
371 result.internalSetParent(null); | |
372 if (fChildren.isEmpty()) | |
373 fChildren= null; | |
374 return result; | |
375 } | |
376 | |
377 /** | |
378 * Removes the first occurrence of the given child from the list | |
379 * of children. | |
380 * | |
381 * @param child the child to be removed | |
382 * @return <code>true</code> if the edit contained the given | |
383 * child; otherwise <code>false</code> is returned | |
384 */ | |
385 public final bool removeChild(TextEdit child) { | |
386 Assert.isNotNull(child); | |
387 if (fChildren is null) | |
388 return false; | |
389 bool result= fChildren.remove(child); | |
390 if (result) { | |
391 child.internalSetParent(null); | |
392 if (fChildren.isEmpty()) | |
393 fChildren= null; | |
394 } | |
395 return result; | |
396 } | |
397 | |
398 /** | |
399 * Removes all child edits from and returns them. The parent | |
400 * of the removed edits is set to <code>null</code>. | |
401 * | |
402 * @return an array of the removed edits | |
403 */ | |
404 public final TextEdit[] removeChildren() { | |
405 if (fChildren is null) | |
406 return EMPTY_ARRAY; | |
407 int size= fChildren.size(); | |
408 TextEdit[] result= new TextEdit[size]; | |
409 for (int i= 0; i < size; i++) { | |
410 result[i]= (TextEdit)fChildren.get(i); | |
411 result[i].internalSetParent(null); | |
412 } | |
413 fChildren= null; | |
414 return result; | |
415 } | |
416 | |
417 /** | |
418 * Returns <code>true</code> if this edit has children. Otherwise | |
419 * <code>false</code> is returned. | |
420 * | |
421 * @return <code>true</code> if this edit has children; otherwise | |
422 * <code>false</code> is returned | |
423 */ | |
424 public final bool hasChildren() { | |
425 return fChildren !is null && ! fChildren.isEmpty(); | |
426 } | |
427 | |
428 /** | |
429 * Returns the edit's children. If the edit doesn't have any | |
430 * children an empty array is returned. | |
431 * | |
432 * @return the edit's children | |
433 */ | |
434 public final TextEdit[] getChildren() { | |
435 if (fChildren is null) | |
436 return EMPTY_ARRAY; | |
437 return (TextEdit[])fChildren.toArray(new TextEdit[fChildren.size()]); | |
438 } | |
439 | |
440 /** | |
441 * Returns the size of the managed children. | |
442 * | |
443 * @return the size of the children | |
444 */ | |
445 public final int getChildrenSize() { | |
446 if (fChildren is null) | |
447 return 0; | |
448 return fChildren.size(); | |
449 } | |
450 | |
451 /** | |
452 * Returns the text range spawned by the given array of text edits. | |
453 * The method requires that the given array contains at least one | |
454 * edit. If all edits passed are deleted the method returns <code> | |
455 * null</code>. | |
456 * | |
457 * @param edits an array of edits | |
458 * @return the text range spawned by the given array of edits or | |
459 * <code>null</code> if all edits are marked as deleted | |
460 */ | |
461 public static IRegion getCoverage(TextEdit[] edits) { | |
462 Assert.isTrue(edits !is null && edits.length > 0); | |
463 | |
464 int offset= Integer.MAX_VALUE; | |
465 int end= Integer.MIN_VALUE; | |
466 int deleted= 0; | |
467 for (int i= 0; i < edits.length; i++) { | |
468 TextEdit edit= edits[i]; | |
469 if (edit.isDeleted()) { | |
470 deleted++; | |
471 } else { | |
472 offset= Math.min(offset, edit.getOffset()); | |
473 end= Math.max(end, edit.getExclusiveEnd()); | |
474 } | |
475 } | |
476 if (edits.length is deleted) | |
477 return null; | |
478 | |
479 return new Region(offset, end - offset); | |
480 } | |
481 | |
482 /* | |
483 * Hook called before this edit gets added to the passed | |
484 * parent. | |
485 */ | |
486 void aboutToBeAdded(TextEdit parent) { | |
487 } | |
488 | |
489 //---- Object methods ------------------------------------------------------ | |
490 | |
491 /** | |
492 * The <code>Edit</code> implementation of this <code>Object</code> | |
493 * method uses object identity (is). | |
494 * | |
495 * @param obj the other object | |
496 * @return <code>true</code> iff <code>this is obj</code>; otherwise | |
497 * <code>false</code> is returned | |
498 * | |
499 * @see Object#equals(java.lang.Object) | |
500 */ | |
501 public final bool equals(Object obj) { | |
502 return this is obj; // equivalent to Object.equals | |
503 } | |
504 | |
505 /** | |
506 * The <code>Edit</code> implementation of this <code>Object</code> | |
507 * method calls uses <code>Object#hashCode()</code> to compute its | |
508 * hash code. | |
509 * | |
510 * @return the object's hash code value | |
511 * | |
512 * @see Object#hashCode() | |
513 */ | |
514 public final int hashCode() { | |
515 return super.hashCode(); | |
516 } | |
517 | |
518 /* | |
519 * @see java.lang.Object#toString() | |
520 */ | |
521 public String toString() { | |
522 StringBuffer buffer= new StringBuffer(); | |
523 toStringWithChildren(buffer, 0); | |
524 return buffer.toString(); | |
525 } | |
526 | |
527 /** | |
528 * Adds the string representation of this text edit without | |
529 * children information to the given string buffer. | |
530 * | |
531 * @param buffer the string buffer | |
532 * @param indent the indent level in number of spaces | |
533 * @since 3.3 | |
534 */ | |
535 void internalToString(StringBuffer buffer, int indent) { | |
536 for (int i= indent; i > 0; i--) { | |
537 buffer.append(" "); //$NON-NLS-1$ | |
538 } | |
539 buffer.append("{"); //$NON-NLS-1$ | |
540 String name= getClass().getName(); | |
541 int index= name.lastIndexOf('.'); | |
542 if (index !is -1) { | |
543 buffer.append(name.substring(index + 1)); | |
544 } else { | |
545 buffer.append(name); | |
546 } | |
547 buffer.append("} "); //$NON-NLS-1$ | |
548 if (isDeleted()) { | |
549 buffer.append("[deleted]"); //$NON-NLS-1$ | |
550 } else { | |
551 buffer.append("["); //$NON-NLS-1$ | |
552 buffer.append(getOffset()); | |
553 buffer.append(","); //$NON-NLS-1$ | |
554 buffer.append(getLength()); | |
555 buffer.append("]"); //$NON-NLS-1$ | |
556 } | |
557 } | |
558 | |
559 /** | |
560 * Adds the string representation for this text edit | |
561 * and its children to the given string buffer. | |
562 * | |
563 * @param buffer the string buffer | |
564 * @param indent the indent level in number of spaces | |
565 * @since 3.3 | |
566 */ | |
567 private void toStringWithChildren(StringBuffer buffer, int indent) { | |
568 internalToString(buffer, indent); | |
569 if (fChildren !is null) { | |
570 for (Iterator iterator= fChildren.iterator(); iterator.hasNext();) { | |
571 TextEdit child= (TextEdit) iterator.next(); | |
572 buffer.append('\n'); | |
573 child.toStringWithChildren(buffer, indent + 1); | |
574 } | |
575 } | |
576 } | |
577 | |
578 //---- Copying ------------------------------------------------------------- | |
579 | |
580 /** | |
581 * Creates a deep copy of the edit tree rooted at this | |
582 * edit. | |
583 * | |
584 * @return a deep copy of the edit tree | |
585 * @see #doCopy() | |
586 */ | |
587 public final TextEdit copy() { | |
588 TextEditCopier copier= new TextEditCopier(this); | |
589 return copier.perform(); | |
590 } | |
591 | |
592 /** | |
593 * Creates and returns a copy of this edit. The copy method should be | |
594 * implemented in a way so that the copy can executed without causing | |
595 * any harm to the original edit. Implementors of this method are | |
596 * responsible for creating deep or shallow copies of referenced | |
597 * object to fulfill this requirement. | |
598 * <p> | |
599 * Implementers of this method should use the copy constructor <code> | |
600 * Edit#Edit(Edit source) to initialize the edit part of the copy. | |
601 * Implementors aren't responsible to actually copy the children or | |
602 * to set the right parent. | |
603 * <p> | |
604 * This method <b>should not be called</b> from outside the framework. | |
605 * Please use <code>copy</code> to create a copy of a edit tree. | |
606 * | |
607 * @return a copy of this edit. | |
608 * @see #copy() | |
609 * @see #postProcessCopy(TextEditCopier) | |
610 * @see TextEditCopier | |
611 */ | |
612 protected abstract TextEdit doCopy(); | |
613 | |
614 /** | |
615 * This method is called on every edit of the copied tree to do some | |
616 * post-processing like connected an edit to a different edit in the tree. | |
617 * <p> | |
618 * This default implementation does nothing | |
619 * | |
620 * @param copier the copier that manages a map between original and | |
621 * copied edit. | |
622 * @see TextEditCopier | |
623 */ | |
624 protected void postProcessCopy(TextEditCopier copier) { | |
625 } | |
626 | |
627 //---- Visitor support ------------------------------------------------- | |
628 | |
629 /** | |
630 * Accepts the given visitor on a visit of the current edit. | |
631 * | |
632 * @param visitor the visitor object | |
633 * @exception IllegalArgumentException if the visitor is null | |
634 */ | |
635 public final void accept(TextEditVisitor visitor) { | |
636 Assert.isNotNull(visitor); | |
637 // begin with the generic pre-visit | |
638 visitor.preVisit(this); | |
639 // dynamic dispatch to internal method for type-specific visit/endVisit | |
640 accept0(visitor); | |
641 // end with the generic post-visit | |
642 visitor.postVisit(this); | |
643 } | |
644 | |
645 /** | |
646 * Accepts the given visitor on a type-specific visit of the current edit. | |
647 * This method must be implemented in all concrete text edits. | |
648 * <p> | |
649 * General template for implementation on each concrete TextEdit class: | |
650 * <pre> | |
651 * <code> | |
652 * bool visitChildren= visitor.visit(this); | |
653 * if (visitChildren) { | |
654 * acceptChildren(visitor); | |
655 * } | |
656 * </code> | |
657 * </pre> | |
658 * Note that the caller (<code>accept</code>) takes care of invoking | |
659 * <code>visitor.preVisit(this)</code> and <code>visitor.postVisit(this)</code>. | |
660 * </p> | |
661 * | |
662 * @param visitor the visitor object | |
663 */ | |
664 protected abstract void accept0(TextEditVisitor visitor); | |
665 | |
666 | |
667 /** | |
668 * Accepts the given visitor on the edits children. | |
669 * <p> | |
670 * This method must be used by the concrete implementations of | |
671 * <code>accept</code> to traverse list-values properties; it | |
672 * encapsulates the proper handling of on-the-fly changes to the list. | |
673 * </p> | |
674 * | |
675 * @param visitor the visitor object | |
676 */ | |
677 protected final void acceptChildren(TextEditVisitor visitor) { | |
678 if (fChildren is null) | |
679 return; | |
680 Iterator iterator= fChildren.iterator(); | |
681 while (iterator.hasNext()) { | |
682 TextEdit curr= (TextEdit) iterator.next(); | |
683 curr.accept(visitor); | |
684 } | |
685 } | |
686 | |
687 //---- Execution ------------------------------------------------------- | |
688 | |
689 /** | |
690 * Applies the edit tree rooted by this edit to the given document. To check | |
691 * if the edit tree can be applied to the document either catch <code> | |
692 * MalformedTreeException</code> or use <code>TextEditProcessor</code> to | |
693 * execute an edit tree. | |
694 * | |
695 * @param document the document to be manipulated | |
696 * @param style flags controlling the execution of the edit tree. Valid | |
697 * flags are: <code>CREATE_UNDO</code> and </code>UPDATE_REGIONS</code>. | |
698 * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise | |
699 * <code>null</code> is returned. | |
700 * | |
701 * @exception MalformedTreeException is thrown if the tree isn't | |
702 * in a valid state. This exception is thrown before any edit | |
703 * is executed. So the document is still in its original state. | |
704 * @exception BadLocationException is thrown if one of the edits | |
705 * in the tree can't be executed. The state of the document is | |
706 * undefined if this exception is thrown. | |
707 * | |
708 * @see TextEditProcessor#performEdits() | |
709 */ | |
710 public final UndoEdit apply(IDocument document, int style) throws MalformedTreeException, BadLocationException { | |
711 try { | |
712 TextEditProcessor processor= new TextEditProcessor(document, this, style); | |
713 return processor.performEdits(); | |
714 } finally { | |
715 // disconnect from text edit processor | |
716 fParent= null; | |
717 } | |
718 } | |
719 | |
720 /** | |
721 * Applies the edit tree rooted by this edit to the given document. This | |
722 * method is a convenience method for <code>apply(document, CREATE_UNDO | UPDATE_REGIONS) | |
723 * </code> | |
724 * | |
725 * @param document the document to which to apply this edit | |
726 * @return a undo edit, if <code>CREATE_UNDO</code> is specified. Otherwise | |
727 * <code>null</code> is returned. | |
728 * @exception MalformedTreeException is thrown if the tree isn't | |
729 * in a valid state. This exception is thrown before any edit | |
730 * is executed. So the document is still in its original state. | |
731 * @exception BadLocationException is thrown if one of the edits | |
732 * in the tree can't be executed. The state of the document is | |
733 * undefined if this exception is thrown. | |
734 * @see #apply(IDocument, int) | |
735 */ | |
736 public final UndoEdit apply(IDocument document) throws MalformedTreeException, BadLocationException { | |
737 return apply(document, CREATE_UNDO | UPDATE_REGIONS); | |
738 } | |
739 | |
740 UndoEdit dispatchPerformEdits(TextEditProcessor processor) throws BadLocationException { | |
741 return processor.executeDo(); | |
742 } | |
743 | |
744 void dispatchCheckIntegrity(TextEditProcessor processor) throws MalformedTreeException { | |
745 processor.checkIntegrityDo(); | |
746 } | |
747 | |
748 //---- internal state accessors ---------------------------------------------------------- | |
749 | |
750 void internalSetParent(TextEdit parent) { | |
751 if (parent !is null) | |
752 Assert.isTrue(fParent is null); | |
753 fParent= parent; | |
754 } | |
755 | |
756 void internalSetOffset(int offset) { | |
757 Assert.isTrue(offset >= 0); | |
758 fOffset= offset; | |
759 } | |
760 | |
761 void internalSetLength(int length) { | |
762 Assert.isTrue(length >= 0); | |
763 fLength= length; | |
764 } | |
765 | |
766 List internalGetChildren() { | |
767 return fChildren; | |
768 } | |
769 | |
770 void internalSetChildren(List children) { | |
771 fChildren= children; | |
772 } | |
773 | |
774 void internalAdd(TextEdit child) throws MalformedTreeException { | |
775 child.aboutToBeAdded(this); | |
776 if (child.isDeleted()) | |
777 throw new MalformedTreeException(this, child, TextEditMessages.getString("TextEdit.deleted_edit")); //$NON-NLS-1$ | |
778 if (!covers(child)) | |
779 throw new MalformedTreeException(this, child, TextEditMessages.getString("TextEdit.range_outside")); //$NON-NLS-1$ | |
780 if (fChildren is null) { | |
781 fChildren= new ArrayList(2); | |
782 } | |
783 int index= computeInsertionIndex(child); | |
784 fChildren.add(index, child); | |
785 child.internalSetParent(this); | |
786 } | |
787 | |
788 private int computeInsertionIndex(TextEdit edit) throws MalformedTreeException { | |
789 int size= fChildren.size(); | |
790 if (size is 0) | |
791 return 0; | |
792 int lastIndex= size - 1; | |
793 TextEdit last= (TextEdit)fChildren.get(lastIndex); | |
794 if (last.getExclusiveEnd() <= edit.getOffset()) | |
795 return size; | |
796 try { | |
797 | |
798 int index= Collections.binarySearch(fChildren, edit, INSERTION_COMPARATOR); | |
799 | |
800 if (index < 0) | |
801 // edit is not in fChildren | |
802 return -index - 1; | |
803 | |
804 // edit is already in fChildren | |
805 // make sure that multiple insertion points at the same offset are inserted last. | |
806 while (index < lastIndex && INSERTION_COMPARATOR.compare(fChildren.get(index), fChildren.get(index + 1)) is 0) | |
807 index++; | |
808 | |
809 return index + 1; | |
810 | |
811 } catch(MalformedTreeException e) { | |
812 e.setParent(this); | |
813 throw e; | |
814 } | |
815 } | |
816 | |
817 //---- Offset & Length updating ------------------------------------------------- | |
818 | |
819 /** | |
820 * Adjusts the edits offset according to the given | |
821 * delta. This method doesn't update any children. | |
822 * | |
823 * @param delta the delta of the text replace operation | |
824 */ | |
825 void adjustOffset(int delta) { | |
826 if (isDeleted()) | |
827 return; | |
828 fOffset+= delta; | |
829 Assert.isTrue(fOffset >= 0); | |
830 } | |
831 | |
832 /** | |
833 * Adjusts the edits length according to the given | |
834 * delta. This method doesn't update any children. | |
835 * | |
836 * @param delta the delta of the text replace operation | |
837 */ | |
838 void adjustLength(int delta) { | |
839 if (isDeleted()) | |
840 return; | |
841 fLength+= delta; | |
842 Assert.isTrue(fLength >= 0); | |
843 } | |
844 | |
845 /** | |
846 * Marks the edit as deleted. This method doesn't update | |
847 * any children. | |
848 */ | |
849 void markAsDeleted() { | |
850 fOffset= DELETED_VALUE; | |
851 fLength= DELETED_VALUE; | |
852 } | |
853 | |
854 //---- Edit processing ---------------------------------------------- | |
855 | |
856 /** | |
857 * Traverses the edit tree to perform the consistency check. | |
858 * | |
859 * @param processor the text edit processor | |
860 * @param document the document to be manipulated | |
861 * @param sourceEdits the list of source edits to be performed before | |
862 * the actual tree is applied to the document | |
863 * | |
864 * @return the number of indirect move or copy target edit children | |
865 */ | |
866 int traverseConsistencyCheck(TextEditProcessor processor, IDocument document, List sourceEdits) { | |
867 int result= 0; | |
868 if (fChildren !is null) { | |
869 for (int i= fChildren.size() - 1; i >= 0; i--) { | |
870 TextEdit child= (TextEdit)fChildren.get(i); | |
871 result= Math.max(result, child.traverseConsistencyCheck(processor, document, sourceEdits)); | |
872 } | |
873 } | |
874 if (processor.considerEdit(this)) { | |
875 performConsistencyCheck(processor, document); | |
876 } | |
877 return result; | |
878 } | |
879 | |
880 void performConsistencyCheck(TextEditProcessor processor, IDocument document) { | |
881 } | |
882 | |
883 void traverseSourceComputation(TextEditProcessor processor, IDocument document) { | |
884 } | |
885 | |
886 void performSourceComputation(TextEditProcessor processor, IDocument document) { | |
887 } | |
888 | |
889 int traverseDocumentUpdating(TextEditProcessor processor, IDocument document) throws BadLocationException { | |
890 int delta= 0; | |
891 if (fChildren !is null) { | |
892 for (int i= fChildren.size() - 1; i >= 0; i--) { | |
893 TextEdit child= (TextEdit)fChildren.get(i); | |
894 delta+= child.traverseDocumentUpdating(processor, document); | |
895 childDocumentUpdated(); | |
896 } | |
897 } | |
898 if (processor.considerEdit(this)) { | |
899 if (delta !is 0) | |
900 adjustLength(delta); | |
901 int r= performDocumentUpdating(document); | |
902 if (r !is 0) | |
903 adjustLength(r); | |
904 delta+= r; | |
905 } | |
906 return delta; | |
907 } | |
908 | |
909 /** | |
910 * Hook method called when the document updating of a child edit has been | |
911 * completed. When a client calls {@link #apply(IDocument)} or | |
912 * {@link #apply(IDocument, int)} this method is called | |
913 * {@link #getChildrenSize()} times. | |
914 * <p> | |
915 * May be overridden by subclasses of {@link MultiTextEdit}. | |
916 * | |
917 * @since 3.1 | |
918 */ | |
919 protected void childDocumentUpdated() { | |
920 } | |
921 | |
922 abstract int performDocumentUpdating(IDocument document) throws BadLocationException; | |
923 | |
924 int traverseRegionUpdating(TextEditProcessor processor, IDocument document, int accumulatedDelta, bool delete) { | |
925 performRegionUpdating(accumulatedDelta, delete); | |
926 if (fChildren !is null) { | |
927 bool childDelete= delete || deleteChildren(); | |
928 for (Iterator iter= fChildren.iterator(); iter.hasNext();) { | |
929 TextEdit child= (TextEdit)iter.next(); | |
930 accumulatedDelta= child.traverseRegionUpdating(processor, document, accumulatedDelta, childDelete); | |
931 childRegionUpdated(); | |
932 } | |
933 } | |
934 return accumulatedDelta + fDelta; | |
935 } | |
936 | |
937 /** | |
938 * Hook method called when the region updating of a child edit has been | |
939 * completed. When a client calls {@link #apply(IDocument)} this method is | |
940 * called {@link #getChildrenSize()} times. When calling | |
941 * {@link #apply(IDocument, int)} this method is called | |
942 * {@link #getChildrenSize()} times, when the style parameter contains the | |
943 * {@link #UPDATE_REGIONS} flag. | |
944 * <p> | |
945 * May be overridden by subclasses of {@link MultiTextEdit}. | |
946 * | |
947 * @since 3.1 | |
948 */ | |
949 protected void childRegionUpdated() { | |
950 } | |
951 | |
952 void performRegionUpdating(int accumulatedDelta, bool delete) { | |
953 if (delete) | |
954 markAsDeleted(); | |
955 else | |
956 adjustOffset(accumulatedDelta); | |
957 } | |
958 | |
959 abstract bool deleteChildren(); | |
960 | |
961 void internalMoveTree(int delta) { | |
962 adjustOffset(delta); | |
963 if (fChildren !is null) { | |
964 for (Iterator iter= fChildren.iterator(); iter.hasNext();) { | |
965 ((TextEdit)iter.next()).internalMoveTree(delta); | |
966 } | |
967 } | |
968 } | |
969 | |
970 void deleteTree() { | |
971 markAsDeleted(); | |
972 if (fChildren !is null) { | |
973 for (Iterator iter= fChildren.iterator(); iter.hasNext();) { | |
974 TextEdit child= (TextEdit)iter.next(); | |
975 child.deleteTree(); | |
976 } | |
977 } | |
978 } | |
979 } | |
980 |