KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > editor > fold > FoldHierarchyTransactionImpl


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.modules.editor.fold;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Iterator JavaDoc;
26 import java.util.List JavaDoc;
27 import java.util.Map JavaDoc;
28 import java.util.Set JavaDoc;
29 import javax.swing.event.DocumentEvent JavaDoc;
30 import javax.swing.text.BadLocationException JavaDoc;
31 import javax.swing.text.Document JavaDoc;
32 import org.netbeans.api.editor.fold.Fold;
33 import org.netbeans.api.editor.fold.FoldHierarchyEvent;
34 import org.netbeans.api.editor.fold.FoldHierarchyListener;
35 import org.netbeans.api.editor.fold.FoldStateChange;
36 import org.netbeans.api.editor.fold.FoldType;
37 import org.netbeans.api.editor.fold.FoldUtilities;
38 import org.netbeans.modules.editor.fold.FoldUtilitiesImpl;
39 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
40 import org.netbeans.spi.editor.fold.FoldManager;
41 import org.openide.ErrorManager;
42
43 /**
44  * Class encapsulating a modification
45  * of the code folding hierarchy.
46  * <br>
47  * It's provided by {@link RootFold#createHierarchyTransaction()}.
48  * <br>
49  * It can accumulate arbitrary number of changes of various folds.
50  * <br>
51  * Only one transaction can be active at the time.
52  * <br>
53  * Once all the modifications are done the transaction must be
54  * committed by {@link #commit()} which creates
55  * a {@link org.netbeans.api.editor.fold.FoldHierarchyEvent}
56  * and fires it to the listeners automatically.
57  * <br>
58  * Once the transaction is committed no additional
59  * changes can be made to it.
60  * <br>
61  * There is currently no way to rollback the transaction.
62  *
63  * @author Miloslav Metelka
64  * @version 1.00
65  */

66
67 public final class FoldHierarchyTransactionImpl {
68     
69     private static final boolean debug
70         = Boolean.getBoolean("netbeans.debug.editor.fold");
71     
72     private static final Fold[] EMPTY_FOLDS = new Fold[0];
73
74     private static final FoldStateChange[] EMPTY_FOLD_STATE_CHANGES
75         = new FoldStateChange[0];
76     
77     private static final int[] EMPTY_INT_ARRAY = new int[0];
78     
79     
80     private FoldHierarchyTransaction transaction;
81
82     private boolean committed;
83     
84     private FoldHierarchyExecution execution;
85     
86     /**
87      * Fold inside which the last operation (insert or remove)
88      * was done.
89      */

90     private Fold lastOperationFold;
91
92     /**
93      * Index at which the last operation (insert or remove) was done.
94      */

95     private int lastOperationIndex;
96     
97     /**
98      * Fold that is block in case the inspectOverlap() returns null.
99      * <br>
100      * This is instance var so that inspectOverlap() can set it.
101      */

102     private Fold addFoldBlock;
103     
104     /**
105      * List of lists of folds that were unblocked by removing
106      * of a blocked fold indexed by the fold priority.
107      * <br>
108      * Prior to the commit of the transaction the unblocked
109      * folds are attempted to be reinserted into the hierarchy
110      * starting with folds with the highest priority
111      * going to folds with the lowest priority.
112      */

113     private List JavaDoc unblockedFoldLists = new ArrayList JavaDoc(4);
114     
115     /**
116      * Maximum priority of the unblocked folds added
117      * since the start of this transaction.
118      */

119     private int unblockedFoldMaxPriority = -1;
120     
121     /**
122      * Set of folds that were added to the hierarchy
123      * during this transaction.
124      */

125     private Set JavaDoc addedToHierarchySet;
126     
127     /**
128      * Set of folds that were removed from the hierarchy
129      * during this transaction.
130      */

131     private Set JavaDoc removedFromHierarchySet;
132
133     private Map JavaDoc fold2StateChange;
134     
135     private int affectedStartOffset;
136     
137     private int affectedEndOffset;
138     
139     
140     public FoldHierarchyTransactionImpl(FoldHierarchyExecution execution) {
141         this.execution = execution;
142         this.affectedStartOffset = Integer.MAX_VALUE;
143         this.affectedEndOffset = -1;
144         
145         this.transaction = SpiPackageAccessor.get().createFoldHierarchyTransaction(this);
146     }
147     
148     public FoldHierarchyTransaction getTransaction() {
149         return transaction;
150     }
151
152     /**
153      * Commit this active transaction.
154      * <br>
155      * The <code>FoldHierarchyEvent</code> will be fired automatically
156      * (if there were any changes done during this transaction).
157      * <br>
158      * The transaction can only be commited once.
159      */

160     public void commit() {
161         checkNotCommitted();
162
163         /**
164          * Mark the transaction as committed now
165          * to prevent problems in case one of the listeners fails later.
166          */

167         committed = true;
168         execution.clearActiveTransaction();
169
170         if (!isEmpty()) {
171             
172             int size;
173             Fold[] removedFolds;
174             if (removedFromHierarchySet != null && ((size = removedFromHierarchySet.size()) != 0)) {
175                 removedFolds = new Fold[size];
176                 removedFromHierarchySet.toArray(removedFolds);
177
178             } else {
179                 removedFolds = EMPTY_FOLDS;
180             }
181             
182             Fold[] addedFolds;
183             if (addedToHierarchySet != null && ((size = addedToHierarchySet.size()) != 0)) {
184                 addedFolds = new Fold[size];
185                 addedToHierarchySet.toArray(addedFolds);
186
187             } else {
188                 addedFolds = EMPTY_FOLDS;
189             }
190
191             FoldStateChange[] stateChanges;
192             if (fold2StateChange != null) {
193                 stateChanges = new FoldStateChange[fold2StateChange.size()];
194                 fold2StateChange.values().toArray(stateChanges);
195             } else { // no state changes => use empty array
196
stateChanges = EMPTY_FOLD_STATE_CHANGES;
197             }
198             
199             for (int i = stateChanges.length - 1; i >= 0; i--) {
200                 FoldStateChange change = stateChanges[i];
201                 Fold fold = change.getFold();
202                 updateAffectedOffsets(fold);
203                 int origOffset = change.getOriginalStartOffset();
204                 if (origOffset != -1) {
205                     updateAffectedStartOffset(origOffset);
206                 }
207                 origOffset = change.getOriginalEndOffset();
208                 if (origOffset != -1) {
209                     updateAffectedEndOffset(origOffset);
210                 }
211             }
212
213             execution.createAndFireFoldHierarchyEvent(
214                 removedFolds, addedFolds, stateChanges,
215                 affectedStartOffset, affectedEndOffset
216             );
217         }
218     }
219     
220     /**
221      * This method implements the <code>DocumentListener</code>.
222      * <br>
223      * It is not intended to be called by clients.
224      */

225     public void insertUpdate(DocumentEvent JavaDoc evt) {
226         // Check whether there was an insert done right
227
// at the original ending offset of the fold
228
// so the fold end offset should be moved back.
229
if (debug) {
230             /*DEBUG*/System.err.println("insertUpdate: offset=" + evt.getOffset() // NOI18N
231
+ ", length=" + evt.getLength()); // NOI18N
232
}
233
234         try {
235             insertCheckEndOffset(execution.getRootFold(), evt);
236
237         } catch (BadLocationException JavaDoc e) {
238             ErrorManager.getDefault().notify(e);
239         }
240     }
241     
242     private void insertCheckEndOffset(Fold fold, DocumentEvent JavaDoc evt)
243     throws BadLocationException JavaDoc {
244         int insertEndOffset = evt.getOffset() + evt.getLength();
245         // Find first fold that starts at (or best represents) the insertEndOffset
246
int childIndex = FoldUtilitiesImpl.findFoldStartIndex(fold, insertEndOffset, false);
247         if (childIndex >= 0) { // could be at end of the child fold with the index
248
Fold childFold = fold.getFold(childIndex);
249             // Check whether not in fact searching for previous fold
250
if (childIndex > 0 && childFold.getStartOffset() == insertEndOffset) {
251                 childIndex--;
252                 childFold = fold.getFold(childIndex);
253             }
254             
255             int childFoldEndOffset = childFold.getEndOffset();
256             // Check whether the child fold "contains" the insert
257
// i.e. the children of the child must be checked as well
258
if (childFoldEndOffset >= insertEndOffset) { // check children
259
// Must dig into children first to maintain consistency
260
// in case when the last child fold would end right at end offset
261
// of this child.
262
insertCheckEndOffset(childFold, evt);
263
264                 // Inform the fold about insertion
265
ApiPackageAccessor.get().foldInsertUpdate(childFold, evt);
266
267                 if (childFoldEndOffset == insertEndOffset) {
268                     // Now correct the end offset to the one before insertion
269
setEndOffset(childFold, evt.getDocument(), evt.getOffset());
270                     
271                 } else { // not right at the end of the fold -> check damaged
272
ApiPackageAccessor api = ApiPackageAccessor.get();
273                     if (api.foldIsStartDamaged(childFold) || api.foldIsEndDamaged(childFold)) {
274                         execution.remove(childFold, this);
275                         removeDamagedNotify(childFold);
276                         
277                         if (debug) {
278                             /*DEBUG*/System.err.println("insertUpdate: removed damaged " // NOI18N
279
+ childFold);
280                         }
281                     }
282                 }
283             }
284         }
285     }
286     
287     private FoldOperationImpl getOperation(Fold fold) {
288         return ApiPackageAccessor.get().foldGetOperation(fold);
289     }
290     
291     private FoldManager getManager(Fold fold) {
292         return getOperation(fold).getManager();
293     }
294     
295     private void setEndOffset(Fold fold, Document JavaDoc doc, int endOffset)
296     throws BadLocationException JavaDoc {
297         int origEndOffset = fold.getEndOffset();
298         ApiPackageAccessor api = ApiPackageAccessor.get();
299         api.foldSetEndOffset(fold, doc, endOffset);
300         api.foldStateChangeEndOffsetChanged(getFoldStateChange(fold), origEndOffset);
301     }
302
303     public void setCollapsed(Fold fold, boolean collapsed) {
304         boolean oldCollapsed = fold.isCollapsed();
305         if (oldCollapsed != collapsed) {
306             ApiPackageAccessor api = ApiPackageAccessor.get();
307             api.foldSetCollapsed(fold, collapsed);
308             api.foldStateChangeCollapsedChanged(getFoldStateChange(fold));
309         }
310     }
311     
312     private void removeDamagedNotify(Fold fold) {
313         getManager(fold).removeDamagedNotify(fold);
314     }
315     
316     private void removeEmptyNotify(Fold fold) {
317         getManager(fold).removeEmptyNotify(fold);
318     }
319     
320     /**
321      * This method implements the <code>DocumentListener</code>.
322      * <br>
323      * It is not intended to be called by clients.
324      */

325     public void removeUpdate(DocumentEvent JavaDoc evt) {
326         // Check whether the remove damaged any folds
327
// or made them empty.
328
if (debug) {
329             /*DEBUG*/System.err.println("removeUpdate: offset=" + evt.getOffset());
330         }
331
332         removeCheckDamaged(execution.getRootFold(), evt);
333     }
334     
335     private void removeCheckDamaged(Fold fold, DocumentEvent JavaDoc evt) {
336         ApiPackageAccessor api = ApiPackageAccessor.get();
337         int childIndex = FoldUtilitiesImpl.findFoldStartIndex(fold, evt.getOffset(), true);
338         if (childIndex >= 0) {
339             boolean removed;
340             do {
341                 Fold childFold = fold.getFold(childIndex);
342                 removed = false;
343                 if (FoldUtilities.isEmpty(childFold)) {
344                     removeCheckDamaged(childFold, evt); // nest prior removing
345
execution.remove(childFold, this);
346                     getManager(childFold).removeEmptyNotify(childFold);
347                     removed = true;
348
349                     if (debug) {
350                         /*DEBUG*/System.err.println("insertUpdate: removed empty " // NOI18N
351
+ childFold);
352                     }
353
354                 } else if (api.foldIsStartDamaged(childFold) || api.foldIsEndDamaged(childFold)) {
355                     removeCheckDamaged(childFold, evt); // nest prior removing
356
execution.remove(childFold, this);
357                     getManager(childFold).removeDamagedNotify(childFold);
358                     removed = true;
359
360                     if (debug) {
361                         /*DEBUG*/System.err.println("insertUpdate: removed damaged " // NOI18N
362
+ childFold);
363                     }
364
365                 } else if (childFold.getFoldCount() > 0) { // check children
366
// Some children could be damaged even if this one was not
367
removeCheckDamaged(childFold, evt);
368                 }
369                 
370                 // Check whether the expand is necessary
371
if (!removed) { // only if not removed yet
372
if (childFold.isCollapsed() && api.foldIsExpandNecessary(childFold)) {
373                         setCollapsed(childFold , false);
374                     }
375                     
376                     api.foldRemoveUpdate(childFold, evt);
377                 }
378
379                 // intentionally do not increase childIndex
380
} while (removed && childIndex < fold.getFoldCount());
381         }
382     }
383     
384     private boolean isEmpty() {
385         return (fold2StateChange == null || fold2StateChange.size() == 0)
386             && (addedToHierarchySet == null || addedToHierarchySet.size() == 0)
387             && (removedFromHierarchySet == null || removedFromHierarchySet.size() == 0);
388     }
389
390     public FoldStateChange getFoldStateChange(Fold fold) {
391         if (fold2StateChange == null) {
392             fold2StateChange = new HashMap JavaDoc();
393         }
394         
395         FoldStateChange change = (FoldStateChange)fold2StateChange.get(fold);
396         if (change == null) {
397             change = ApiPackageAccessor.get().createFoldStateChange(fold);
398             fold2StateChange.put(fold, change);
399         }
400
401         return change;
402     }
403
404     /**
405      * Remove the fold either from the hierarchy or from the blocked list.
406      */

407     void removeFold(Fold fold) {
408         if (debug) {
409             /*DEBUG*/System.err.println("removeFold: " + fold);
410         }
411
412         Fold parent = fold.getParent();
413         if (parent != null) { // present in hierarchy
414
int index = parent.getFoldIndex(fold);
415             removeFoldFromHierarchy(parent, index, null); // no block passed here
416

417             lastOperationFold = parent;
418             lastOperationIndex = index;
419
420         } else { // not present in hierarchy - must be blocked (or error)
421
if (!execution.isBlocked(fold)) { // not blocked i.e. already removed
422
throw new IllegalStateException JavaDoc("Fold already removed: " + fold); // NOI18N
423
}
424             execution.unmarkBlocked(fold);
425             // If the fold was blocking other folds then unblock them here
426
unblockBlocked(fold);
427         }
428         
429         processUnblocked(); // attempt to reinsert unblocked folds
430
}
431     
432     /**
433      * Remove all present folds in the hierarchy
434      * once the managers are going to be switched.
435      */

436     void removeAllFolds(Fold[] allBlocked) {
437         // First remove all blocked folds
438
for (int i = allBlocked.length - 1; i >= 0; i--) {
439             removeFold(allBlocked[i]);
440         }
441         
442         removeAllChildrenAndSelf(execution.getRootFold());
443     }
444     
445     private void removeAllChildrenAndSelf(Fold fold) {
446         int foldCount = fold.getFoldCount();
447         if (foldCount > 0) {
448             for (int i = foldCount - 1; i >= 0; i--) {
449                 removeAllChildrenAndSelf(fold.getFold(i));
450             }
451         }
452         if (!FoldUtilities.isRootFold(fold)) {
453             removeFold(fold);
454         }
455     }
456     
457     public void changedUpdate(DocumentEvent JavaDoc evt) {
458         // No explicit checking actions upon document change notification
459
}
460
461     /**
462      * Called by FoldHierarchySpi to attempt to insert
463      * the fold into hierarchy. It's also possible that
464      * the fold cannot be inserted and will be added to the list
465      * of blocked folds.
466      *
467      * @param fold fold to add
468      * @return true if the fold was successfully added to hierarchy
469      * or false if it could not be added and became blocked.
470      */

471     boolean addFold(Fold fold) {
472         if (debug) {
473             /*DEBUG*/System.err.println("addFold: " + fold); // NOI18N
474
}
475
476         return addFold(fold, null);
477     }
478     
479     /**
480      * Recursive method to add fold under the given parent.
481      *
482      * @param fold non-null fold to be inserted into hierarchy
483      * @param parentFold parent fold under which to insert. If it's null
484      * then attempt to use hints from lastOperationFold and lastOperationIndex.
485      * The explicit passing of root fold can be used to force to ignore the hints.
486      * @return true if the fold was successfully added or false if it became blocked.
487      */

488     private boolean addFold(Fold fold, Fold parentFold) {
489         int foldStartOffset = fold.getStartOffset();
490         int foldEndOffset = fold.getEndOffset();
491         int foldPriority = getOperation(fold).getPriority();
492         
493         int index;
494         boolean useLast; // use hints from lastOperationFold and lastOperationIndex
495
if (parentFold == null) { // attempt to guess
496
parentFold = lastOperationFold;
497             if (parentFold == null // no valid guess
498
|| foldStartOffset < parentFold.getStartOffset()
499                 || foldEndOffset > parentFold.getEndOffset()
500             ) { // Use root fold
501
parentFold = execution.getRootFold();
502                 index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset);
503                 useLast = false;
504             } else {
505                 index = lastOperationIndex;
506                 useLast = true;
507             }
508
509         } else { // already valid parentFold (do not use last* vars)
510
index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset);
511             useLast = false;
512         }
513         
514         // Check whether the index is withing bounds
515
int foldCount = parentFold.getFoldCount();
516         if (useLast && index > foldCount) {
517             index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset);
518             useLast = false;
519         }
520
521         // Fill in the prevFold variable
522
// and verify that the guessed index is correct - startOffset
523
// of the prev fold must be lower than foldStartOffset
524
// and start offset of the next fold must be greater than foldStartOffset
525

526         Fold prevFold; // fold that precedes fold being added
527
if (index > 0) {
528             prevFold = parentFold.getFold(index - 1);
529             if (useLast && foldStartOffset < prevFold.getStartOffset()) { // bad guess
530
index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset);
531                 useLast = false;
532                 prevFold = (index > 0) ? parentFold.getFold(index - 1) : null;
533             }
534
535         } else { // index == 0
536
prevFold = null;
537         }
538
539         // Fold that will follow the fold being inserted
540
// By default guess it's the fold at "index" but it may be a fold
541
// at higher index as well.
542
Fold nextFold;
543         if (index < foldCount) { // next fold exists
544
nextFold = parentFold.getFold(index);
545             if (useLast && foldStartOffset >= nextFold.getStartOffset()) { // bad guess
546
index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset);
547                 useLast = false;
548                 prevFold = (index > 0) ? parentFold.getFold(index - 1) : null;
549                 nextFold = (index < foldCount) ? parentFold.getFold(index) : null;
550             }
551
552         } else { // index >= foldCount
553
nextFold = null;
554         }
555
556         // Check whether the fold to be added overlaps
557
// with previous fold (it's start offset is before end offset
558
// of the previous fold.
559
// Check whether end offset of the fold
560
// does not overlap with folds that would follow it
561
boolean blocked;
562         // Index hints:
563
// null - no overlapping (clear insert of start offset)
564
// length == 0 - overlapping but no children
565
// length > 0 - overlapping and children - see inspectOverlap()
566
int[] prevOverlapIndexes;
567         if (prevFold != null && foldStartOffset < prevFold.getEndOffset()) { // overlap
568
if (foldEndOffset <= prevFold.getEndOffset()) { // fold fully nested
569
// Nest into prevFold
570
return addFold(fold, prevFold);
571                 
572             } else { // fold overlaps with prevFold
573
if (foldPriority > getOperation(prevFold).getPriority()) { // can replace
574
if (prevFold.getFoldCount() > 0) { // must check children too
575
prevOverlapIndexes = inspectOverlap(prevFold,
576                             foldStartOffset, foldPriority, 1);
577
578                         if (prevOverlapIndexes == null) { // blocked
579
// "addFoldBlock" var was assigned by inspectOverlap()
580
blocked = true;
581                         } else { // not blocked
582
blocked = false;
583                         }
584
585                     } else { // prevFold has no children
586
blocked = false;
587                         prevOverlapIndexes = EMPTY_INT_ARRAY;
588                     }
589                 } else { // cannot remove -> overlaps
590
blocked = true;
591                     addFoldBlock = prevFold;
592                     prevOverlapIndexes = null;
593                 }
594             }
595
596         } else { // no overlapping with prevFold -> insert after
597
blocked = false;
598             prevOverlapIndexes = null;
599         }
600
601
602         if (!blocked) {
603             // Which fold will be the next important for the insert (possibly overlapped)
604
int nextIndex = index;
605             // Non-null in case of active overlapping for foldEndOffset
606
int[] nextOverlapIndexes = null;
607             if (nextFold != null) { // next fold exists
608
if (foldEndOffset > nextFold.getStartOffset()) {
609                     // End inside or after the current fold
610
if (foldEndOffset >= nextFold.getEndOffset()) {
611                         // Fold ends after end offset of the current nextFold
612
// Find the fold in (or after) which the inserted fold really ends.
613
// Do binary search to have deterministic non-linear perf
614
// Third param is false i.e. get possibly last fold
615
// in multiple empty folds (same like in findFoldInsertIndex())
616
nextIndex = FoldUtilitiesImpl.findFoldStartIndex(parentFold,
617                         foldEndOffset, false);
618
619                         // nextIndex should not be -1 - otherwise should not reach this code
620
nextFold = parentFold.getFold(nextIndex);
621                     }
622
623                     if (foldEndOffset < nextFold.getEndOffset()) { // ends inside
624
if (foldPriority > getOperation(nextFold).getPriority()) { // remove next fold
625
if (nextFold.getFoldCount() > 0) { // next has children
626
nextOverlapIndexes = inspectOverlap(nextFold, foldEndOffset, foldPriority, 1);
627                                 if (nextOverlapIndexes == null) { // blocked
628
// "addFoldBlock" var was assigned by inspectOverlap()
629
blocked = true;
630                                 } // can remove nested folds
631

632                             } else { // nextFold has no children => can be removed
633
nextOverlapIndexes = EMPTY_INT_ARRAY;
634                             }
635
636                         } else { // blocked by next fold
637
blocked = true;
638                             addFoldBlock = nextFold;
639                         }
640
641                     } else { // fold ends after bounds of nextFold but prior start of next fold
642
nextIndex++; // insert clearly after the nextFold
643
}
644
645                 } // fold ends before start offset of nextFold => insert normally later
646
} // next fold does not exist - no folds at index or after it
647

648             
649             if (!blocked) {
650                 // Here it should be possible to insert the fold
651
// prevOverlapIndexes and nextOverlapIndexes need to be resolved first
652
// (and the possible index shift consequences)
653
// Finally the lastOperationFold and lastOperationIndex
654
// should be set for future use.
655

656                 if (prevOverlapIndexes != null) {
657                     int replaceIndexShift;
658                     if (prevOverlapIndexes.length == 0) { // no children
659
replaceIndexShift = 0;
660                     } else { // children
661
replaceIndexShift = removeOverlap(prevFold,
662                             prevOverlapIndexes, fold);
663                         // Must shift nextIndex by number of replaced children
664
nextIndex += prevFold.getFoldCount();
665                     }
666
667                     removeFoldFromHierarchy(parentFold, index - 1, fold);
668                     index += replaceIndexShift - 1; // -1 for removed prevFold
669
nextIndex--; // -1 for removed prevFold
670
}
671                 
672                 if (nextOverlapIndexes != null) {
673                     int replaceIndexShift;
674                     if (nextOverlapIndexes.length == 0) { // no children
675
replaceIndexShift = 0;
676                     } else { // children
677
replaceIndexShift = removeOverlap(nextFold,
678                             nextOverlapIndexes, fold);
679                     }
680                     
681                     removeFoldFromHierarchy(parentFold, nextIndex, fold);
682                     nextIndex += replaceIndexShift;
683                 }
684                 
685                 ApiPackageAccessor.get().foldExtractToChildren(parentFold, index, nextIndex - index, fold);
686                 
687                 // Update affected offsets
688
updateAffectedOffsets(fold);
689                 markFoldAddedToHierarchy(fold);
690                 processUnblocked();
691             }
692         }
693         
694         if (blocked) {
695              // Fold is blocked - "addFoldBlock" var holds the blocker
696
execution.markBlocked(fold, addFoldBlock);
697             addFoldBlock = null; // enable GC
698
}
699         
700         // Remember hints for next call
701
lastOperationFold = parentFold;
702         lastOperationIndex = index + 1;
703         
704         return !blocked;
705     }
706     
707     /**
708      * Nested check of possibility of inserting a fold.
709      *
710      * @param fold that has at least one child fold. Folds with empty
711      * children cannot be used here.
712      * @param offset of inserting
713      * @param priority of the fold
714      * @param level nesting level of check - starting at 1
715      * @return array of ints containing deepest-level + 1 entries
716      * where each item presents the index of the overlapped item
717      * that needs to be removed. The first array item
718      * is either 0 - clean insert after the index inside the deepest level
719      * or 1 - overlapping but removable (has no children).
720      * <br>
721      * <code>null</code> is returned if folds overlap but priority
722      * of present fold is higher so the attempted fold will become blocked.
723      * <code>checkOverlapBlock</code> will be filled with the deep fold
724      * that actually blocks.
725      *
726      * <p>
727      * Example:<pre>
728      * [0] = 0 - clean insert after fold at index 5
729      * [1] = 2 - overlapping with fold at index 2 at level 1
730      * [2] = 5 - deepest level ?overlapping? => no, index 0 says clean insert
731      * </pre>
732      *
733      * <p>
734      * Example 2:<pre>
735      * [0] = 1 - overlap with fold at index 4 (fold will be removed)
736      * [1] = 1 - overlapping with fold at index 1 at level 1
737      * [2] = 4 - deepest level ?overlapping? => yes, index 0 says overlapping
738      * </pre>
739
740      */

741     private int[] inspectOverlap(Fold fold, int offset, int priority, int level) {
742         int index = FoldUtilitiesImpl.findFoldStartIndex(fold, offset, false);
743         int[] result;
744         Fold indexFold;
745         if (index >= 0 && FoldUtilities.containsOffset(
746             (indexFold = fold.getFold(index)), offset)
747         ) {
748             if (priority > getOperation(indexFold).getPriority()) { // can be replaced
749
if (indexFold.getFoldCount() > 0) { // has non-empty children
750
result = inspectOverlap(indexFold, offset, priority, level + 1);
751                     if (result != null) { // no blocking in children
752
result[level] = index;
753                     } // result == null => blocking in children
754

755                 } else { // has no or empty children
756
result = new int[level + 1];
757                     result[0] = 1; // overlapping at the last level
758
result[level] = index; // will later insert at index 0
759
}
760             } else { // higher priority of existing fold -> return null
761
addFoldBlock = indexFold; // remember the blocking fold
762
result = null;
763             }
764
765         } else { // before first child fold or no overlapping
766
result = new int[level + 1];
767             result[0] = 0; // clearly nested
768
result[level] = index; // will later insert at index 0
769
}
770         return result;
771     }
772     
773     /**
774      * Remove overlapping folds based on information from previous call
775      * to <code>inspectOverlap()</code>.
776      *
777      * @param fold fold which blocking children will be removed. The fold itself
778      * will remain (must be removed by caller).
779      * @param indexes indexes array obtained by previous call to inspectOverlap().
780      * @param block blocking fold that will be used when marking the removed
781      * children as blocked.
782      *
783      * @return fold insert index that corresponds to the originally used offset.
784      */

785     private int removeOverlap(Fold fold, int[] indexes, Fold block) {
786         int indexShift = 0; // how many new children was inserted prior to offset
787
int indexesLengthM1 = indexes.length - 1;
788         for (int i = 1; i < indexesLengthM1; i++) {
789             int index = indexes[i] + indexShift;
790             removeFoldFromHierarchy(fold, index, block);
791             indexShift += index;
792         }
793         
794         // Need to process last (most inner) fold
795
int index = indexes[indexesLengthM1] + indexShift;
796         if (indexes[0] == 0) { // clearly nested after the fold
797
index++; // move after the fold
798
} else { // indexes[0] == 1 => remove the overlap fold
799
removeFoldFromHierarchy(fold, index, block);
800         }
801         return index;
802     }
803     
804     /**
805      * Physically remove the fold from the hierarchy and update the appropriate
806      * state variables.
807      */

808     private void removeFoldFromHierarchy(Fold parentFold, int index, Fold block) {
809         Fold removedFold = ApiPackageAccessor.get().foldReplaceByChildren(parentFold, index);
810         updateAffectedOffsets(removedFold);
811         markFoldRemovedFromHierarchy(removedFold);
812         unblockBlocked(removedFold);
813         if (block != null) {
814             execution.markBlocked(removedFold, block);
815         }
816     }
817     
818     /**
819      * Remove the block that was removed from hierarchy
820      * because of adding of another fold. Remember
821      * all folds that were blocked by the remove block
822      * because they will be attempted to be reinserted
823      * prior committing of this transaction.
824      */

825     private void unblockBlocked(Fold block) {
826         Set JavaDoc blockedSet = execution.unmarkBlock(block);
827         if (blockedSet != null) {
828             for (Iterator JavaDoc it = blockedSet.iterator(); it.hasNext();) {
829                 Fold blocked = (Fold)it.next();
830                 int priority = getOperation(blocked).getPriority();
831                 while (unblockedFoldLists.size() <= priority) {
832                     unblockedFoldLists.add(new ArrayList JavaDoc(4));
833                 }
834                 ((List JavaDoc)unblockedFoldLists.get(priority)).add(blocked);
835                 if (priority > unblockedFoldMaxPriority) {
836                     unblockedFoldMaxPriority = priority;
837                 }
838             }
839         }
840     }
841
842     /**
843      * Attempt to reinsert the folds unblocked by particular add/remove operation.
844      */

845     private void processUnblocked() {
846         if (unblockedFoldMaxPriority >= 0) { // some folds became unblocked
847
for (int priority = unblockedFoldMaxPriority; priority >= 0; priority--) {
848                 List JavaDoc foldList = (List JavaDoc)unblockedFoldLists.get(priority);
849                 Fold rootFold = execution.getRootFold();
850                 for (int i = foldList.size() - 1; i >= 0; i--) {
851                     // Remove last fold from the list
852
Fold unblocked = (Fold)foldList.remove(i);
853                     
854                     if (!execution.isAddedOrBlocked(unblocked)) { // not yet processed
855
unblockedFoldMaxPriority = -1;
856
857                         // Attempt to reinsert the fold - random order - use root fold
858
addFold(unblocked, rootFold);
859
860                         if (unblockedFoldMaxPriority >= priority) {
861                             throw new IllegalStateException JavaDoc("Folds removed with priority=" // NOI18N
862
+ unblockedFoldMaxPriority);
863                         }
864                         if (foldList.size() != i) {
865                             throw new IllegalStateException JavaDoc("Same priority folds removed"); // NOI18N
866
}
867                     }
868                 }
869             }
870         }
871         unblockedFoldMaxPriority = -1;
872     }
873
874     private void markFoldAddedToHierarchy(Fold fold) {
875         // Check and remove from removedFromHierarchySet if marked removed
876
if (removedFromHierarchySet == null || !removedFromHierarchySet.remove(fold)) {
877             if (addedToHierarchySet == null) {
878                 addedToHierarchySet = new HashSet JavaDoc();
879             }
880             addedToHierarchySet.add(fold);
881         }
882     }
883     
884     private void markFoldRemovedFromHierarchy(Fold fold) {
885         // Check and remove from addedToHierarchySet if marked added
886
if (addedToHierarchySet == null || !addedToHierarchySet.remove(fold)) {
887             if (removedFromHierarchySet == null) {
888                 removedFromHierarchySet = new HashSet JavaDoc();
889             }
890             removedFromHierarchySet.add(fold);
891         }
892     }
893     
894     
895     private void updateAffectedOffsets(Fold fold) {
896         updateAffectedStartOffset(fold.getStartOffset());
897         updateAffectedEndOffset(fold.getEndOffset());
898     }
899
900     /**
901      * Extend affectedStartOffset in downward direction.
902      */

903     private void updateAffectedStartOffset(int offset) {
904         if (offset < affectedStartOffset) {
905             affectedStartOffset = offset;
906         }
907     }
908             
909     /**
910      * Extend affectedEndOffset in upward direction.
911      */

912     private void updateAffectedEndOffset(int offset) {
913         if (offset > affectedEndOffset) {
914             affectedEndOffset = offset;
915         }
916     }
917             
918     private void checkNotCommitted() {
919         if (committed) {
920             throw new IllegalStateException JavaDoc("FoldHierarchyChange already committed."); // NOI18N
921
}
922     }
923     
924 }
925
Popular Tags