1 19 20 package org.netbeans.modules.editor.fold; 21 22 import java.beans.PropertyChangeEvent ; 23 import java.beans.PropertyChangeListener ; 24 import java.util.ArrayList ; 25 import java.util.Collection ; 26 import java.util.HashMap ; 27 import java.util.HashSet ; 28 import java.util.Iterator ; 29 import java.util.List ; 30 import java.util.Map ; 31 import java.util.Set ; 32 import javax.swing.SwingUtilities ; 33 import javax.swing.event.DocumentEvent ; 34 import javax.swing.event.DocumentListener ; 35 import javax.swing.event.EventListenerList ; 36 import javax.swing.text.AbstractDocument ; 37 import javax.swing.text.BadLocationException ; 38 import javax.swing.text.Document ; 39 import javax.swing.text.JTextComponent ; 40 import org.netbeans.api.editor.fold.Fold; 41 import org.netbeans.api.editor.fold.FoldHierarchy; 42 import org.netbeans.api.editor.fold.FoldHierarchyEvent; 43 import org.netbeans.api.editor.fold.FoldHierarchyListener; 44 import org.netbeans.api.editor.fold.FoldStateChange; 45 import org.netbeans.lib.editor.util.swing.DocumentListenerPriority; 46 import org.netbeans.lib.editor.util.swing.DocumentUtilities; 47 import org.netbeans.spi.editor.fold.FoldManager; 48 import org.netbeans.spi.editor.fold.FoldManagerFactory; 49 import org.netbeans.spi.editor.fold.FoldOperation; 50 import org.netbeans.lib.editor.util.PriorityMutex; 51 import org.openide.ErrorManager; 52 53 75 76 public final class FoldHierarchyExecution implements DocumentListener { 77 78 private static final String PROPERTY_FOLD_HIERARCHY_MUTEX = "foldHierarchyMutex"; 80 private static final String PROPERTY_FOLDING_ENABLED = "code-folding-enable"; 82 private static final boolean debug 83 = Boolean.getBoolean("netbeans.debug.editor.fold"); 85 private static final boolean debugFire 86 = Boolean.getBoolean("netbeans.debug.editor.fold.fire"); 88 private static final FoldOperationImpl[] EMPTY_FOLD_OPERTAION_IMPL_ARRAY 89 = new FoldOperationImpl[0]; 90 91 static { 92 FoldOperation.isBoundsValid(0, 0, 0, 0); 94 } 95 96 private final JTextComponent component; 97 98 private FoldHierarchy hierarchy; 99 100 private Fold rootFold; 101 102 private FoldOperationImpl[] operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY; 103 104 107 private Map blocked2block = new HashMap (4); 108 109 112 private Map block2blockedSet = new HashMap (4); 113 114 115 private boolean inited; 116 117 private AbstractDocument lastDocument; 118 119 private PriorityMutex mutex; 120 121 private EventListenerList listenerList; 122 123 private boolean foldingEnabled; 124 125 private FoldHierarchyTransactionImpl activeTransaction; 126 127 private PropertyChangeListener componentChangesListener; 128 129 public static synchronized FoldHierarchy getOrCreateFoldHierarchy(JTextComponent component) { 130 if (component == null) { 131 throw new NullPointerException ("component cannot be null"); } 133 134 FoldHierarchyExecution execution 135 = (FoldHierarchyExecution)component.getClientProperty(FoldHierarchyExecution.class); 136 137 if (execution == null) { 138 execution = new FoldHierarchyExecution(component); 139 execution.init(); 140 141 component.putClientProperty(FoldHierarchyExecution.class, execution); 142 } 143 144 return execution.getHierarchy(); 145 } 146 147 153 private FoldHierarchyExecution(JTextComponent component) { 154 this.component = component; 155 } 156 157 164 private void init() { 165 listenerList = new EventListenerList (); 167 168 mutex = (PriorityMutex)component.getClientProperty(PROPERTY_FOLD_HIERARCHY_MUTEX); 170 if (mutex == null) { 171 mutex = new PriorityMutex(); 172 component.putClientProperty(PROPERTY_FOLD_HIERARCHY_MUTEX, mutex); 173 } 174 175 this.hierarchy = ApiPackageAccessor.get().createFoldHierarchy(this); 176 177 Document doc = component.getDocument(); 178 try { 179 rootFold = ApiPackageAccessor.get().createFold( 180 new FoldOperationImpl(this, null, Integer.MAX_VALUE), 181 FoldHierarchy.ROOT_FOLD_TYPE, 182 "root", false, 184 doc, 185 0, doc.getEndPosition().getOffset(), 186 0, 0, 187 null 188 ); 189 } catch (BadLocationException e) { 190 ErrorManager.getDefault().notify(e); 191 } 192 193 foldingEnabled = getFoldingEnabledSetting(); 194 195 startComponentChangesListening(); 197 198 rebuild(); 199 } 200 201 205 public final FoldHierarchy getHierarchy() { 206 return hierarchy; 207 } 208 209 239 public final void lock() { 240 mutex.lock(); 241 } 242 243 247 public void unlock() { 248 mutex.unlock(); 249 } 250 251 258 public JTextComponent getComponent() { 259 return component; 260 } 261 262 270 public Fold getRootFold() { 271 return rootFold; 272 } 273 274 281 public void addFoldHierarchyListener(FoldHierarchyListener l) { 282 synchronized (listenerList) { 283 listenerList.add(FoldHierarchyListener.class, l); 284 } 285 } 286 287 294 public void removeFoldHierarchyListener(FoldHierarchyListener l) { 295 synchronized (listenerList) { 296 listenerList.remove(FoldHierarchyListener.class, l); 297 } 298 } 299 300 void fireFoldHierarchyListener(FoldHierarchyEvent evt) { 301 if (debugFire) { 302 System.err.println("Firing FoldHierarchyEvent:\n" + evt); } 304 305 Object [] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { 307 if (listeners[i] == FoldHierarchyListener.class) { 308 ((FoldHierarchyListener)listeners[i + 1]).foldHierarchyChanged(evt); 309 } 310 } 311 312 } 313 314 327 public boolean add(Fold fold, FoldHierarchyTransactionImpl transaction) { 328 if (fold.getParent() != null || isBlocked(fold)) { 329 throw new IllegalStateException ("Fold already added: " + fold); } 331 332 boolean added = transaction.addFold(fold); 333 334 336 return added; 337 } 338 339 346 public void remove(Fold fold, FoldHierarchyTransactionImpl transaction) { 347 transaction.removeFold(fold); 348 349 } 351 352 358 public boolean isAddedOrBlocked(Fold fold) { 359 return (fold.getParent() != null || isBlocked(fold)); 360 } 361 362 365 public boolean isBlocked(Fold fold) { 366 return (getBlock(fold) != null); 367 } 368 369 373 Fold getBlock(Fold fold) { 374 return (blocked2block.size() > 0) 375 ? (Fold)blocked2block.get(fold) 376 : null; 377 } 378 379 382 void markBlocked(Fold blocked, Fold block) { 383 blocked2block.put(blocked, block); 384 385 Set blockedSet = (Set )block2blockedSet.get(block); 386 if (blockedSet == null) { 387 blockedSet = new HashSet (); 388 block2blockedSet.put(block, blockedSet); 389 } 390 if (!blockedSet.add(blocked)) { throw new IllegalStateException ("fold " + blocked + " already blocked"); } 393 } 394 395 402 Fold unmarkBlocked(Fold blocked) { 403 Fold block = (Fold)blocked2block.remove(blocked); 405 if (block == null) { throw new IllegalArgumentException ("Not blocked: " + blocked); } 408 409 Set blockedSet = (Set )block2blockedSet.get(block); 411 if (!blockedSet.remove(blocked)) { 412 throw new IllegalStateException ("Not blocker for " + blocked); } 414 if (blockedSet.size() == 0) { block2blockedSet.remove(block); 416 } 417 return block; 418 } 419 420 428 Set unmarkBlock(Fold block) { 429 Set blockedSet = (Set )block2blockedSet.remove(block); 430 if (blockedSet != null) { 431 int size = blocked2block.size(); 433 blocked2block.keySet().removeAll(blockedSet); 434 if (size - blocked2block.size() != blockedSet.size()) { throw new IllegalStateException ("Not all removed: " + blockedSet); } 437 } 438 return blockedSet; 439 } 440 441 446 public void collapse(Collection c) { 447 setCollapsed(c, true); 448 } 449 450 455 public void expand(Collection c) { 456 setCollapsed(c, false); 457 } 458 459 private void setCollapsed(Collection c, boolean collapsed) { 460 FoldHierarchyTransactionImpl transaction = openTransaction(); 461 try { 462 for (Iterator it = c.iterator(); it.hasNext();) { 463 Fold fold = (Fold)it.next(); 464 transaction.setCollapsed(fold, collapsed); 465 } 466 } finally { 467 transaction.commit(); 468 } 469 } 470 471 497 public FoldHierarchyTransactionImpl openTransaction() { 498 if (activeTransaction != null) { 499 throw new IllegalStateException ("Active transaction already exists."); } 501 activeTransaction = new FoldHierarchyTransactionImpl(this); 502 return activeTransaction; 503 } 504 505 void clearActiveTransaction() { 506 if (activeTransaction == null) { 507 throw new IllegalStateException ("No transaction in progress"); } 509 activeTransaction = null; 510 } 511 512 void createAndFireFoldHierarchyEvent( 513 Fold[] removedFolds, Fold[] addedFolds, 514 FoldStateChange[] foldStateChanges, 515 int affectedStartOffset, int affectedEndOffset) { 516 517 if (affectedStartOffset < 0) { 519 throw new IllegalArgumentException ("affectedStartOffset=" + affectedStartOffset + " < 0"); } 522 523 if (affectedEndOffset < affectedStartOffset) { 524 throw new IllegalArgumentException ("affectedEndOffset=" + affectedEndOffset + " < affectedStartOffset=" + affectedStartOffset); } 527 528 FoldHierarchyEvent evt = ApiPackageAccessor.get().createFoldHierarchyEvent( 529 hierarchy, 530 removedFolds, addedFolds, foldStateChanges, 531 affectedStartOffset, affectedEndOffset 532 ); 533 534 fireFoldHierarchyListener(evt); 535 } 536 537 540 public void rebuild() { 541 if (lastDocument != null) { 543 DocumentUtilities.removeDocumentListener(lastDocument, this, DocumentListenerPriority.FOLD_UPDATE); 545 lastDocument = null; 546 } 547 548 Document doc = getComponent().getDocument(); 549 AbstractDocument adoc; 550 boolean releaseOnly; if (doc instanceof AbstractDocument ) { 552 adoc = (AbstractDocument )doc; 553 releaseOnly = false; 554 } else { adoc = null; 556 releaseOnly = true; 557 } 558 559 if (!foldingEnabled) { releaseOnly = true; 561 } 562 563 if (adoc != null) { 564 adoc.readLock(); 565 566 if (!releaseOnly) { 568 lastDocument = adoc; 569 DocumentUtilities.addDocumentListener(lastDocument, this, DocumentListenerPriority.FOLD_UPDATE); 571 } 572 } 573 try { 574 lock(); 575 try { 576 rebuildManagers(releaseOnly); 577 } finally { 578 unlock(); 579 } 580 } finally { 581 if (adoc != null) { 582 adoc.readUnlock(); 583 } 584 } 585 } 586 587 593 private void rebuildManagers(boolean releaseOnly) { 594 for (int i = 0; i < operations.length; i++) { 595 operations[i].release(); 596 } 597 operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY; 599 FoldManagerFactoryProvider provider = !releaseOnly 601 ? FoldManagerFactoryProvider.getDefault() 602 : FoldManagerFactoryProvider.getEmpty(); 603 604 List factoryList = provider.getFactoryList(getHierarchy()); 605 int factoryListLength = factoryList.size(); 606 607 if (debug) { 608 System.err.println("FoldHierarchy rebuild():" + " FoldManager factory count=" + factoryListLength ); 611 } 612 613 int priority = factoryListLength - 1; boolean ok = false; 616 try { 617 operations = new FoldOperationImpl[factoryListLength]; 618 for (int i = 0; i < factoryListLength; i++) { 619 FoldManagerFactory factory = (FoldManagerFactory)factoryList.get(i); 620 FoldManager manager = factory.createFoldManager(); 621 operations[i] = new FoldOperationImpl(this, manager, priority); 622 priority--; 623 } 624 ok = true; 625 } finally { 626 if (!ok) { 627 operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY; 628 } 629 } 630 631 FoldHierarchyTransactionImpl transaction = openTransaction(); 633 ok = false; 634 try { 635 Fold[] allBlocked = new Fold[blocked2block.size()]; 637 blocked2block.keySet().toArray(allBlocked); 638 transaction.removeAllFolds(allBlocked); 639 640 for (int i = 0; i < factoryListLength; i++) { 643 operations[i].initFolds(transaction); 644 } 645 ok = true; } finally { 647 if (!ok) { 648 operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY; 650 } 651 transaction.commit(); 652 } 653 } 654 655 private void startComponentChangesListening() { 656 if (componentChangesListener == null) { 657 componentChangesListener = new PropertyChangeListener () { 659 public void propertyChange(PropertyChangeEvent evt) { 660 String propName = evt.getPropertyName(); 661 if ("document".equals(propName)) { rebuild(); 663 } else if (PROPERTY_FOLDING_ENABLED.equals(propName)) { 664 foldingEnabledSettingChange(); 665 } 666 } 667 }; 668 669 getComponent().addPropertyChangeListener(componentChangesListener); 674 } 675 } 676 677 public void insertUpdate(DocumentEvent evt) { 678 lock(); 679 try { 680 FoldHierarchyTransactionImpl transaction = openTransaction(); 681 try { 682 transaction.insertUpdate(evt); 683 684 int operationsLength = operations.length; 685 for (int i = 0; i < operationsLength; i++) { 686 operations[i].insertUpdate(evt, transaction); 687 } 688 } finally { 689 transaction.commit(); 690 } 691 } finally { 692 unlock(); 693 } 694 } 695 696 public void removeUpdate(DocumentEvent evt) { 697 lock(); 698 try { 699 FoldHierarchyTransactionImpl transaction = openTransaction(); 700 try { 701 transaction.removeUpdate(evt); 702 703 int operationsLength = operations.length; 704 for (int i = 0; i < operationsLength; i++) { 705 operations[i].removeUpdate(evt, transaction); 706 } 707 } finally { 708 transaction.commit(); 709 } 710 } finally { 711 unlock(); 712 } 713 } 714 715 public void changedUpdate(DocumentEvent evt) { 716 lock(); 717 try { 718 FoldHierarchyTransactionImpl transaction = openTransaction(); 719 try { 720 transaction.changedUpdate(evt); 721 722 int operationsLength = operations.length; 723 for (int i = 0; i < operationsLength; i++) { 724 operations[i].changedUpdate(evt, transaction); 725 } 726 } finally { 727 transaction.commit(); 728 } 729 } finally { 730 unlock(); 731 } 732 } 733 734 private boolean getFoldingEnabledSetting() { 735 Boolean b = (Boolean )component.getClientProperty(PROPERTY_FOLDING_ENABLED); 736 return (b != null) ? b.booleanValue() : true; 737 } 738 739 public void foldingEnabledSettingChange() { 740 boolean origFoldingEnabled = foldingEnabled; 741 foldingEnabled = getFoldingEnabledSetting(); 742 if (origFoldingEnabled != foldingEnabled) { 743 SwingUtilities.invokeLater(new Runnable () { 744 public void run() { 745 lock(); 746 try{ 747 rebuild(); 748 } finally { 749 unlock(); 750 } 751 } 752 }); 753 } 754 } 755 756 762 public void checkConsistency() { 763 try { 764 checkFoldConsistency(getRootFold()); 765 } catch (RuntimeException e) { 766 System.err.println("FOLD HIERARCHY INCONSISTENCY FOUND\n" + this); throw e; } 769 } 770 771 private static void checkFoldConsistency(Fold fold) { 772 int startOffset = fold.getStartOffset(); 773 int endOffset = fold.getEndOffset(); 774 int lastEndOffset = startOffset; 775 776 for (int i = 0; i < fold.getFoldCount(); i++) { 777 Fold child = fold.getFold(i); 778 if (child.getParent() != fold) { 779 throw new IllegalStateException ("Wrong parent of child=" + child + ": " + child.getParent() + " != " + fold); } 783 int foldIndex = fold.getFoldIndex(child); 784 if (foldIndex != i) { 785 throw new IllegalStateException ("Fold index " + foldIndex + " instead of " + i); } 788 789 int childStartOffset = child.getStartOffset(); 790 int childEndOffset = child.getEndOffset(); 791 if (childStartOffset < lastEndOffset) { 792 throw new IllegalStateException ("childStartOffset=" + childStartOffset + " < lastEndOffset=" + lastEndOffset); } 795 if (childStartOffset > childEndOffset) { 796 throw new IllegalStateException ("childStartOffset=" + childStartOffset + " > childEndOffset=" + childEndOffset); } 799 lastEndOffset = childEndOffset; 800 801 checkFoldConsistency(child); 802 } 803 } 804 805 public String toString() { 806 StringBuffer sb = new StringBuffer (); 807 sb.append("component="); sb.append(System.identityHashCode(getComponent())); 809 sb.append('\n'); 810 811 sb.append(FoldUtilitiesImpl.foldToStringChildren(hierarchy.getRootFold(), 0)); 813 sb.append('\n'); 814 815 if (blocked2block != null) { 817 sb.append("BLOCKED\n"); for (Iterator it = blocked2block.entrySet().iterator(); it.hasNext();) { 819 Map.Entry entry = (Map.Entry )it.next(); 820 sb.append(" "); sb.append(entry.getKey()); 822 sb.append(" blocked-by "); sb.append(entry.getValue()); 824 sb.append('\n'); 825 } 826 } 827 828 if (block2blockedSet != null) { 830 sb.append("BLOCKERS\n"); for (Iterator it = block2blockedSet.entrySet().iterator(); it.hasNext();) { 832 Map.Entry entry = (Map.Entry )it.next(); 833 sb.append(" "); sb.append(entry.getKey()); 835 sb.append('\n'); 836 Set blockedSet = (Set )entry.getValue(); 837 for (Iterator it2 = blockedSet.iterator(); it2.hasNext();) { 838 sb.append(" blocks "); sb.append(it2.next()); 840 sb.append('\n'); 841 } 842 } 843 } 844 845 int operationsLength = operations.length; 846 if (operationsLength > 0) { 847 sb.append("Fold Managers\n"); for (int i = 0; i < operationsLength; i++) { 849 sb.append("FOLD MANAGER ["); sb.append(i); 851 sb.append("]:\n"); sb.append(operations[i].getManager()); 853 sb.append("\n"); } 855 } 856 857 return sb.toString(); 858 } 859 860 } 861 | Popular Tags |