1 19 20 package org.netbeans.modules.editor.fold; 21 22 import java.util.ArrayList ; 23 import java.util.HashMap ; 24 import java.util.HashSet ; 25 import java.util.Iterator ; 26 import java.util.List ; 27 import java.util.Map ; 28 import java.util.Set ; 29 import javax.swing.event.DocumentEvent ; 30 import javax.swing.text.BadLocationException ; 31 import javax.swing.text.Document ; 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 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 90 private Fold lastOperationFold; 91 92 95 private int lastOperationIndex; 96 97 102 private Fold addFoldBlock; 103 104 113 private List unblockedFoldLists = new ArrayList (4); 114 115 119 private int unblockedFoldMaxPriority = -1; 120 121 125 private Set addedToHierarchySet; 126 127 131 private Set removedFromHierarchySet; 132 133 private Map 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 160 public void commit() { 161 checkNotCommitted(); 162 163 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 { 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 225 public void insertUpdate(DocumentEvent evt) { 226 if (debug) { 230 System.err.println("insertUpdate: offset=" + evt.getOffset() + ", length=" + evt.getLength()); } 233 234 try { 235 insertCheckEndOffset(execution.getRootFold(), evt); 236 237 } catch (BadLocationException e) { 238 ErrorManager.getDefault().notify(e); 239 } 240 } 241 242 private void insertCheckEndOffset(Fold fold, DocumentEvent evt) 243 throws BadLocationException { 244 int insertEndOffset = evt.getOffset() + evt.getLength(); 245 int childIndex = FoldUtilitiesImpl.findFoldStartIndex(fold, insertEndOffset, false); 247 if (childIndex >= 0) { Fold childFold = fold.getFold(childIndex); 249 if (childIndex > 0 && childFold.getStartOffset() == insertEndOffset) { 251 childIndex--; 252 childFold = fold.getFold(childIndex); 253 } 254 255 int childFoldEndOffset = childFold.getEndOffset(); 256 if (childFoldEndOffset >= insertEndOffset) { insertCheckEndOffset(childFold, evt); 263 264 ApiPackageAccessor.get().foldInsertUpdate(childFold, evt); 266 267 if (childFoldEndOffset == insertEndOffset) { 268 setEndOffset(childFold, evt.getDocument(), evt.getOffset()); 270 271 } else { 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 System.err.println("insertUpdate: removed damaged " + 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 doc, int endOffset) 296 throws BadLocationException { 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 325 public void removeUpdate(DocumentEvent evt) { 326 if (debug) { 329 System.err.println("removeUpdate: offset=" + evt.getOffset()); 330 } 331 332 removeCheckDamaged(execution.getRootFold(), evt); 333 } 334 335 private void removeCheckDamaged(Fold fold, DocumentEvent 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); execution.remove(childFold, this); 346 getManager(childFold).removeEmptyNotify(childFold); 347 removed = true; 348 349 if (debug) { 350 System.err.println("insertUpdate: removed empty " + childFold); 352 } 353 354 } else if (api.foldIsStartDamaged(childFold) || api.foldIsEndDamaged(childFold)) { 355 removeCheckDamaged(childFold, evt); execution.remove(childFold, this); 357 getManager(childFold).removeDamagedNotify(childFold); 358 removed = true; 359 360 if (debug) { 361 System.err.println("insertUpdate: removed damaged " + childFold); 363 } 364 365 } else if (childFold.getFoldCount() > 0) { removeCheckDamaged(childFold, evt); 368 } 369 370 if (!removed) { if (childFold.isCollapsed() && api.foldIsExpandNecessary(childFold)) { 373 setCollapsed(childFold , false); 374 } 375 376 api.foldRemoveUpdate(childFold, evt); 377 } 378 379 } 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 (); 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 407 void removeFold(Fold fold) { 408 if (debug) { 409 System.err.println("removeFold: " + fold); 410 } 411 412 Fold parent = fold.getParent(); 413 if (parent != null) { int index = parent.getFoldIndex(fold); 415 removeFoldFromHierarchy(parent, index, null); 417 lastOperationFold = parent; 418 lastOperationIndex = index; 419 420 } else { if (!execution.isBlocked(fold)) { throw new IllegalStateException ("Fold already removed: " + fold); } 424 execution.unmarkBlocked(fold); 425 unblockBlocked(fold); 427 } 428 429 processUnblocked(); } 431 432 436 void removeAllFolds(Fold[] allBlocked) { 437 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 evt) { 458 } 460 461 471 boolean addFold(Fold fold) { 472 if (debug) { 473 System.err.println("addFold: " + fold); } 475 476 return addFold(fold, null); 477 } 478 479 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; if (parentFold == null) { parentFold = lastOperationFold; 497 if (parentFold == null || foldStartOffset < parentFold.getStartOffset() 499 || foldEndOffset > parentFold.getEndOffset() 500 ) { parentFold = execution.getRootFold(); 502 index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset); 503 useLast = false; 504 } else { 505 index = lastOperationIndex; 506 useLast = true; 507 } 508 509 } else { index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset); 511 useLast = false; 512 } 513 514 int foldCount = parentFold.getFoldCount(); 516 if (useLast && index > foldCount) { 517 index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset); 518 useLast = false; 519 } 520 521 526 Fold prevFold; if (index > 0) { 528 prevFold = parentFold.getFold(index - 1); 529 if (useLast && foldStartOffset < prevFold.getStartOffset()) { index = FoldUtilitiesImpl.findFoldInsertIndex(parentFold, foldStartOffset); 531 useLast = false; 532 prevFold = (index > 0) ? parentFold.getFold(index - 1) : null; 533 } 534 535 } else { prevFold = null; 537 } 538 539 Fold nextFold; 543 if (index < foldCount) { nextFold = parentFold.getFold(index); 545 if (useLast && foldStartOffset >= nextFold.getStartOffset()) { 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 { nextFold = null; 554 } 555 556 boolean blocked; 562 int[] prevOverlapIndexes; 567 if (prevFold != null && foldStartOffset < prevFold.getEndOffset()) { if (foldEndOffset <= prevFold.getEndOffset()) { return addFold(fold, prevFold); 571 572 } else { if (foldPriority > getOperation(prevFold).getPriority()) { if (prevFold.getFoldCount() > 0) { prevOverlapIndexes = inspectOverlap(prevFold, 576 foldStartOffset, foldPriority, 1); 577 578 if (prevOverlapIndexes == null) { blocked = true; 581 } else { blocked = false; 583 } 584 585 } else { blocked = false; 587 prevOverlapIndexes = EMPTY_INT_ARRAY; 588 } 589 } else { blocked = true; 591 addFoldBlock = prevFold; 592 prevOverlapIndexes = null; 593 } 594 } 595 596 } else { blocked = false; 598 prevOverlapIndexes = null; 599 } 600 601 602 if (!blocked) { 603 int nextIndex = index; 605 int[] nextOverlapIndexes = null; 607 if (nextFold != null) { if (foldEndOffset > nextFold.getStartOffset()) { 609 if (foldEndOffset >= nextFold.getEndOffset()) { 611 nextIndex = FoldUtilitiesImpl.findFoldStartIndex(parentFold, 617 foldEndOffset, false); 618 619 nextFold = parentFold.getFold(nextIndex); 621 } 622 623 if (foldEndOffset < nextFold.getEndOffset()) { if (foldPriority > getOperation(nextFold).getPriority()) { if (nextFold.getFoldCount() > 0) { nextOverlapIndexes = inspectOverlap(nextFold, foldEndOffset, foldPriority, 1); 627 if (nextOverlapIndexes == null) { blocked = true; 630 } 632 } else { nextOverlapIndexes = EMPTY_INT_ARRAY; 634 } 635 636 } else { blocked = true; 638 addFoldBlock = nextFold; 639 } 640 641 } else { nextIndex++; } 644 645 } } 648 649 if (!blocked) { 650 656 if (prevOverlapIndexes != null) { 657 int replaceIndexShift; 658 if (prevOverlapIndexes.length == 0) { replaceIndexShift = 0; 660 } else { replaceIndexShift = removeOverlap(prevFold, 662 prevOverlapIndexes, fold); 663 nextIndex += prevFold.getFoldCount(); 665 } 666 667 removeFoldFromHierarchy(parentFold, index - 1, fold); 668 index += replaceIndexShift - 1; nextIndex--; } 671 672 if (nextOverlapIndexes != null) { 673 int replaceIndexShift; 674 if (nextOverlapIndexes.length == 0) { replaceIndexShift = 0; 676 } else { 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 updateAffectedOffsets(fold); 689 markFoldAddedToHierarchy(fold); 690 processUnblocked(); 691 } 692 } 693 694 if (blocked) { 695 execution.markBlocked(fold, addFoldBlock); 697 addFoldBlock = null; } 699 700 lastOperationFold = parentFold; 702 lastOperationIndex = index + 1; 703 704 return !blocked; 705 } 706 707 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()) { if (indexFold.getFoldCount() > 0) { result = inspectOverlap(indexFold, offset, priority, level + 1); 751 if (result != null) { result[level] = index; 753 } 755 } else { result = new int[level + 1]; 757 result[0] = 1; result[level] = index; } 760 } else { addFoldBlock = indexFold; result = null; 763 } 764 765 } else { result = new int[level + 1]; 767 result[0] = 0; result[level] = index; } 770 return result; 771 } 772 773 785 private int removeOverlap(Fold fold, int[] indexes, Fold block) { 786 int indexShift = 0; 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 int index = indexes[indexesLengthM1] + indexShift; 796 if (indexes[0] == 0) { index++; } else { removeFoldFromHierarchy(fold, index, block); 800 } 801 return index; 802 } 803 804 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 825 private void unblockBlocked(Fold block) { 826 Set blockedSet = execution.unmarkBlock(block); 827 if (blockedSet != null) { 828 for (Iterator 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 (4)); 833 } 834 ((List )unblockedFoldLists.get(priority)).add(blocked); 835 if (priority > unblockedFoldMaxPriority) { 836 unblockedFoldMaxPriority = priority; 837 } 838 } 839 } 840 } 841 842 845 private void processUnblocked() { 846 if (unblockedFoldMaxPriority >= 0) { for (int priority = unblockedFoldMaxPriority; priority >= 0; priority--) { 848 List foldList = (List )unblockedFoldLists.get(priority); 849 Fold rootFold = execution.getRootFold(); 850 for (int i = foldList.size() - 1; i >= 0; i--) { 851 Fold unblocked = (Fold)foldList.remove(i); 853 854 if (!execution.isAddedOrBlocked(unblocked)) { unblockedFoldMaxPriority = -1; 856 857 addFold(unblocked, rootFold); 859 860 if (unblockedFoldMaxPriority >= priority) { 861 throw new IllegalStateException ("Folds removed with priority=" + unblockedFoldMaxPriority); 863 } 864 if (foldList.size() != i) { 865 throw new IllegalStateException ("Same priority folds removed"); } 867 } 868 } 869 } 870 } 871 unblockedFoldMaxPriority = -1; 872 } 873 874 private void markFoldAddedToHierarchy(Fold fold) { 875 if (removedFromHierarchySet == null || !removedFromHierarchySet.remove(fold)) { 877 if (addedToHierarchySet == null) { 878 addedToHierarchySet = new HashSet (); 879 } 880 addedToHierarchySet.add(fold); 881 } 882 } 883 884 private void markFoldRemovedFromHierarchy(Fold fold) { 885 if (addedToHierarchySet == null || !addedToHierarchySet.remove(fold)) { 887 if (removedFromHierarchySet == null) { 888 removedFromHierarchySet = new HashSet (); 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 903 private void updateAffectedStartOffset(int offset) { 904 if (offset < affectedStartOffset) { 905 affectedStartOffset = offset; 906 } 907 } 908 909 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 ("FoldHierarchyChange already committed."); } 922 } 923 924 } 925 | Popular Tags |