001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.editor.fold;
043:
044: import java.beans.PropertyChangeEvent;
045: import java.beans.PropertyChangeListener;
046: import java.util.ArrayList;
047: import java.util.Collection;
048: import java.util.HashMap;
049: import java.util.HashSet;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.Map;
053: import java.util.Set;
054: import javax.swing.SwingUtilities;
055: import javax.swing.event.DocumentEvent;
056: import javax.swing.event.DocumentListener;
057: import javax.swing.event.EventListenerList;
058: import javax.swing.text.AbstractDocument;
059: import javax.swing.text.BadLocationException;
060: import javax.swing.text.Document;
061: import javax.swing.text.JTextComponent;
062: import org.netbeans.api.editor.fold.Fold;
063: import org.netbeans.api.editor.fold.FoldHierarchy;
064: import org.netbeans.api.editor.fold.FoldHierarchyEvent;
065: import org.netbeans.api.editor.fold.FoldHierarchyListener;
066: import org.netbeans.api.editor.fold.FoldStateChange;
067: import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
068: import org.netbeans.lib.editor.util.swing.DocumentUtilities;
069: import org.netbeans.spi.editor.fold.FoldManager;
070: import org.netbeans.spi.editor.fold.FoldManagerFactory;
071: import org.netbeans.spi.editor.fold.FoldOperation;
072: import org.netbeans.lib.editor.util.PriorityMutex;
073: import org.openide.ErrorManager;
074:
075: /**
076: * Class backing the <code>FoldHierarchy</code> in one-to-one relationship.
077: * <br>
078: * The <code>FoldHierarchy</code> delegates all its operations
079: * to this object.
080: *
081: * <p>
082: * All the changes performed in to the folds are always done
083: * in terms of a transaction represented by {@link FoldHierarchyTransactionImpl}.
084: * The transaction can be opened by {@link #openTransaction()}.
085: *
086: * <p>
087: * This class changes its state upon displayability change
088: * of the associated component by listening on "ancestor" component property.
089: * <br>
090: * If the component is not displayable then the list of root folds becomes empty
091: * while if the component gets displayable the root folds are created
092: * according to registered managers.
093: *
094: * @author Miloslav Metelka
095: * @version 1.00
096: */
097:
098: public final class FoldHierarchyExecution implements DocumentListener {
099:
100: private static final String PROPERTY_FOLD_HIERARCHY_MUTEX = "foldHierarchyMutex"; //NOI18N
101:
102: private static final String PROPERTY_FOLDING_ENABLED = "code-folding-enable"; //NOI18N
103:
104: private static final boolean debug = Boolean
105: .getBoolean("netbeans.debug.editor.fold"); //NOI18N
106:
107: private static final boolean debugFire = Boolean
108: .getBoolean("netbeans.debug.editor.fold.fire"); //NOI18N
109:
110: private static final FoldOperationImpl[] EMPTY_FOLD_OPERTAION_IMPL_ARRAY = new FoldOperationImpl[0];
111:
112: static {
113: // The following call will make sure that the SpiPackageAccessor gets initialized
114: FoldOperation.isBoundsValid(0, 0, 0, 0);
115: }
116:
117: private final JTextComponent component;
118:
119: private FoldHierarchy hierarchy;
120:
121: private Fold rootFold;
122:
123: private FoldOperationImpl[] operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY;
124:
125: /**
126: * Map containing [blocked-fold, blocking-fold] pairs.
127: */
128: private Map blocked2block = new HashMap(4);
129:
130: /**
131: * Map containing [blocking-fold, blocked-fold-set] pairs.
132: */
133: private Map block2blockedSet = new HashMap(4);
134:
135: /** Whether hierarchy is initialized (root fold etc.) */
136: private boolean inited;
137:
138: private AbstractDocument lastDocument;
139:
140: private PriorityMutex mutex;
141:
142: private EventListenerList listenerList;
143:
144: private boolean foldingEnabled;
145:
146: private FoldHierarchyTransactionImpl activeTransaction;
147:
148: private PropertyChangeListener componentChangesListener;
149:
150: public static synchronized FoldHierarchy getOrCreateFoldHierarchy(
151: JTextComponent component) {
152: if (component == null) {
153: throw new NullPointerException("component cannot be null"); // NOI18N
154: }
155:
156: FoldHierarchyExecution execution = (FoldHierarchyExecution) component
157: .getClientProperty(FoldHierarchyExecution.class);
158:
159: if (execution == null) {
160: execution = new FoldHierarchyExecution(component);
161: execution.init();
162:
163: component.putClientProperty(FoldHierarchyExecution.class,
164: execution);
165: }
166:
167: return execution.getHierarchy();
168: }
169:
170: /**
171: * Construct new fold hierarchy SPI
172: *
173: * @param hierarchy hierarchy for which this SPI gets created.
174: * @param component comoponent for which this all happens.
175: */
176: private FoldHierarchyExecution(JTextComponent component) {
177: this .component = component;
178: }
179:
180: /**
181: * Initialize this spi by existing hierarchy instance
182: * (the one for which this spi was created).
183: * <br>
184: * This is called lazily upon first attempt to lock
185: * the hierarchy.
186: */
187: private void init() {
188: // Allow listeners to be added
189: listenerList = new EventListenerList();
190:
191: // Assign mutex
192: mutex = (PriorityMutex) component
193: .getClientProperty(PROPERTY_FOLD_HIERARCHY_MUTEX);
194: if (mutex == null) {
195: mutex = new PriorityMutex();
196: component.putClientProperty(PROPERTY_FOLD_HIERARCHY_MUTEX,
197: mutex);
198: }
199:
200: this .hierarchy = ApiPackageAccessor.get().createFoldHierarchy(
201: this );
202:
203: Document doc = component.getDocument();
204: try {
205: rootFold = ApiPackageAccessor.get()
206: .createFold(
207: new FoldOperationImpl(this , null,
208: Integer.MAX_VALUE),
209: FoldHierarchy.ROOT_FOLD_TYPE,
210: "root", // NOI18N
211: false, doc, 0,
212: doc.getEndPosition().getOffset(), 0, 0,
213: null);
214: } catch (BadLocationException e) {
215: ErrorManager.getDefault().notify(e);
216: }
217:
218: foldingEnabled = getFoldingEnabledSetting();
219:
220: // Start listening on component changes
221: startComponentChangesListening();
222:
223: rebuild();
224: }
225:
226: /**
227: * Get the fold hierarchy associated with this SPI
228: * in one-to-one relationship.
229: */
230: public final FoldHierarchy getHierarchy() {
231: return hierarchy;
232: }
233:
234: /**
235: * Lock the hierarchy for exclusive use. This method must only
236: * be used together with {@link #unlock()} in <code>try..finally</code> block.
237: * <br>
238: * Prior using this method the document must be locked.
239: * The document lock can be either readlock
240: * e.g. by using {@link javax.swing.text.Document#render(Runnable)}
241: * or writelock
242: * e.g. when in {@link javax.swing.event.DocumentListener})
243: * and must be obtained on component's document
244: * i.e. {@link javax.swing.text.JTextComponent#getDocument()}
245: * should be used.
246: *
247: * <p>
248: * The <code>FoldHierarchy</code> delegates to this method.
249: *
250: * <p>
251: * <font color="red">
252: * <b>Note:</b> The clients using this method must ensure that
253: * they <b>always</b> use this method in the following pattern:<pre>
254: *
255: * lock();
256: * try {
257: * ...
258: * } finally {
259: * unlock();
260: * }
261: * </pre>
262: * </font>
263: */
264: public final void lock() {
265: mutex.lock();
266: }
267:
268: /**
269: * Unlock the hierarchy from exclusive use. This method must only
270: * be used together with {@link #lock()} in <code>try..finally</code> block.
271: */
272: public void unlock() {
273: mutex.unlock();
274: }
275:
276: /**
277: * Get the text component for which this fold hierarchy was created.
278: * <br>
279: * The <code>FoldHierarchy</code> delegates to this method.
280: *
281: * @return non-null text component for which this fold hierarchy was created.
282: */
283: public JTextComponent getComponent() {
284: return component;
285: }
286:
287: /**
288: * Get the root fold of this hierarchy.
289: * <br>
290: * The <code>FoldHierarchy</code> delegates to this method.
291: *
292: * @return root fold of this hierarchy.
293: * The root fold covers the whole document and is uncollapsable.
294: */
295: public Fold getRootFold() {
296: return rootFold;
297: }
298:
299: /**
300: * Add listener for changes done in the hierarchy.
301: * <br>
302: * The <code>FoldHierarchy</code> delegates to this method.
303: *
304: * @param l non-null listener to be added.
305: */
306: public void addFoldHierarchyListener(FoldHierarchyListener l) {
307: synchronized (listenerList) {
308: listenerList.add(FoldHierarchyListener.class, l);
309: }
310: }
311:
312: /**
313: * Remove previously added listener for changes done in the hierarchy.
314: * <br>
315: * The <code>FoldHierarchy</code> delegates to this method.
316: *
317: * @param l non-null listener to be removed.
318: */
319: public void removeFoldHierarchyListener(FoldHierarchyListener l) {
320: synchronized (listenerList) {
321: listenerList.remove(FoldHierarchyListener.class, l);
322: }
323: }
324:
325: void fireFoldHierarchyListener(FoldHierarchyEvent evt) {
326: if (debugFire) {
327: /*DEBUG*/System.err.println("Firing FoldHierarchyEvent:\n"
328: + evt); // NOI18N
329: }
330:
331: Object[] listeners = listenerList.getListenerList(); // no need to sync
332: for (int i = listeners.length - 2; i >= 0; i -= 2) {
333: if (listeners[i] == FoldHierarchyListener.class) {
334: ((FoldHierarchyListener) listeners[i + 1])
335: .foldHierarchyChanged(evt);
336: }
337: }
338:
339: }
340:
341: /**
342: * Attempt to add the given fold to the code folding hierarchy.
343: * The fold will either become part of the hierarchy or it will
344: * become blocked by another fold present in the hierarchy.
345: * <br>
346: * Only folds created by the fold operations of this hierarchy
347: * can be added.
348: *
349: * @param fold fold to be added
350: * @param transaction transaction under which the fold should be added.
351: * @return true if the fold was added successfully or false
352: * if it became blocked.
353: */
354: public boolean add(Fold fold,
355: FoldHierarchyTransactionImpl transaction) {
356: if (fold.getParent() != null || isBlocked(fold)) {
357: throw new IllegalStateException("Fold already added: "
358: + fold); // NOI18N
359: }
360:
361: boolean added = transaction.addFold(fold);
362:
363: // checkConsistency();
364:
365: return added;
366: }
367:
368: /**
369: * Remove the fold that is either present in the hierarchy or blocked
370: * by another fold.
371: *
372: * @param fold fold to be removed
373: * @param transaction non-null transaction under which the fold should be removed.
374: */
375: public void remove(Fold fold,
376: FoldHierarchyTransactionImpl transaction) {
377: transaction.removeFold(fold);
378:
379: // checkConsistency();
380: }
381:
382: /**
383: * Check whether the fold is currently present in the hierarchy or blocked.
384: *
385: * @return true if the fold is currently present in the hierarchy or blocked
386: * or false otherwise.
387: */
388: public boolean isAddedOrBlocked(Fold fold) {
389: return (fold.getParent() != null || isBlocked(fold));
390: }
391:
392: /**
393: * Is the given fold blocked by another fold?
394: */
395: public boolean isBlocked(Fold fold) {
396: return (getBlock(fold) != null);
397: }
398:
399: /**
400: * Get the fold blocking the given fold or null
401: * if the fold is not blocked.
402: */
403: Fold getBlock(Fold fold) {
404: return (blocked2block.size() > 0) ? (Fold) blocked2block
405: .get(fold) : null;
406: }
407:
408: /**
409: * Mark given fold as blocked by the block fold.
410: */
411: void markBlocked(Fold blocked, Fold block) {
412: blocked2block.put(blocked, block);
413:
414: Set blockedSet = (Set) block2blockedSet.get(block);
415: if (blockedSet == null) {
416: blockedSet = new HashSet();
417: block2blockedSet.put(block, blockedSet);
418: }
419: if (!blockedSet.add(blocked)) { // already added
420: throw new IllegalStateException("fold " + blocked
421: + " already blocked"); // NOI18N
422: }
423: }
424:
425: /**
426: * Remove blocked fold from mappings.
427: *
428: * @param blocked fold
429: * @return fold that blocked the blocked fold.
430: * @throws IllegalArgumentException if the given blocked fold was not really blocked.
431: */
432: Fold unmarkBlocked(Fold blocked) {
433: // Find block for the given blocked fold
434: Fold block = (Fold) blocked2block.remove(blocked);
435: if (block == null) { // not blocked
436: throw new IllegalArgumentException("Not blocked: "
437: + blocked); // NOI18N
438: }
439:
440: // Remove the fold from set of blocked folds of the block
441: Set blockedSet = (Set) block2blockedSet.get(block);
442: if (!blockedSet.remove(blocked)) {
443: throw new IllegalStateException("Not blocker for "
444: + blocked); // NOI18N
445: }
446: if (blockedSet.size() == 0) { // Remove the blocker as well
447: block2blockedSet.remove(block);
448: }
449: return block;
450: }
451:
452: /**
453: * Mark the given block fold to be no longer blocking
454: * (and mark the folds blocked by the given block fold as not blocked).
455: *
456: * @param block the fold blocking others
457: * @return set of folds blocked by the block or null if the given fold
458: * was not block.
459: */
460: Set unmarkBlock(Fold block) {
461: Set blockedSet = (Set) block2blockedSet.remove(block);
462: if (blockedSet != null) {
463: // Remove all items of blocked set
464: int size = blocked2block.size();
465: blocked2block.keySet().removeAll(blockedSet);
466: if (size - blocked2block.size() != blockedSet.size()) { // not all removed
467: throw new IllegalStateException("Not all removed: "
468: + blockedSet); // NOI18N
469: }
470: }
471: return blockedSet;
472: }
473:
474: /**
475: * Collapse all folds in the given collection.
476: * <br>
477: * The <code>FoldHierarchy</code> delegates to this method.
478: */
479: public void collapse(Collection c) {
480: setCollapsed(c, true);
481: }
482:
483: /**
484: * Expand all folds in the given collection.
485: * <br>
486: * The <code>FoldHierarchy</code> delegates to this method.
487: */
488: public void expand(Collection c) {
489: setCollapsed(c, false);
490: }
491:
492: private void setCollapsed(Collection c, boolean collapsed) {
493: FoldHierarchyTransactionImpl transaction = openTransaction();
494: try {
495: for (Iterator it = c.iterator(); it.hasNext();) {
496: Fold fold = (Fold) it.next();
497: transaction.setCollapsed(fold, collapsed);
498: }
499: } finally {
500: transaction.commit();
501: }
502: }
503:
504: /**
505: * Open a new transaction on the fold hierarchy
506: * to make a change in the hierarchy.
507: * <br>
508: * Transaction is active until commited
509: * by calling <code>transaction.commit()</code>.
510: * <br>
511: * Only one transaction can be active at the time.
512: * <br>
513: * There is currently no way to rollback the transaction.
514: *
515: * <p>
516: * <font color="red">
517: * <b>Note:</b> The clients using this method must ensure that
518: * they <b>always</b> use this method in the following pattern:<pre>
519: *
520: * FoldHierarchyTransaction transaction = operation.openTransaction();
521: * try {
522: * ...
523: * } finally {
524: * transaction.commit();
525: * }
526: * </pre>
527: * </font>
528:
529: */
530: public FoldHierarchyTransactionImpl openTransaction() {
531: if (activeTransaction != null) {
532: throw new IllegalStateException(
533: "Active transaction already exists."); // NOI18N
534: }
535: activeTransaction = new FoldHierarchyTransactionImpl(this );
536: return activeTransaction;
537: }
538:
539: void clearActiveTransaction() {
540: if (activeTransaction == null) {
541: throw new IllegalStateException(
542: "No transaction in progress"); // NOI18N
543: }
544: activeTransaction = null;
545: }
546:
547: void createAndFireFoldHierarchyEvent(Fold[] removedFolds,
548: Fold[] addedFolds, FoldStateChange[] foldStateChanges,
549: int affectedStartOffset, int affectedEndOffset) {
550:
551: // Check correctness
552: if (affectedStartOffset < 0) {
553: throw new IllegalArgumentException("affectedStartOffset=" // NOI18N
554: + affectedStartOffset + " < 0"); // NOI18N
555: }
556:
557: if (affectedEndOffset < affectedStartOffset) {
558: throw new IllegalArgumentException("affectedEndOffset=" // NOI18N
559: + affectedEndOffset
560: + " < affectedStartOffset="
561: + affectedStartOffset); // NOI18N
562: }
563:
564: FoldHierarchyEvent evt = ApiPackageAccessor.get()
565: .createFoldHierarchyEvent(hierarchy, removedFolds,
566: addedFolds, foldStateChanges,
567: affectedStartOffset, affectedEndOffset);
568:
569: fireFoldHierarchyListener(evt);
570: }
571:
572: /**
573: * Rebuild the fold hierarchy - the fold managers will be recreated.
574: */
575: public void rebuild() {
576: // Stop listening on the original document
577: if (lastDocument != null) {
578: // Remove document listener with specific priority
579: DocumentUtilities.removeDocumentListener(lastDocument,
580: this , DocumentListenerPriority.FOLD_UPDATE);
581: lastDocument = null;
582: }
583:
584: Document doc = getComponent().getDocument();
585: AbstractDocument adoc;
586: boolean releaseOnly; // only release the current hierarchy root folds
587: if (doc instanceof AbstractDocument) {
588: adoc = (AbstractDocument) doc;
589: releaseOnly = false;
590: } else { // doc is null or non-AbstractDocument => release the hierarchy
591: adoc = null;
592: releaseOnly = true;
593: }
594:
595: if (!foldingEnabled) { // folding not enabled => release
596: releaseOnly = true;
597: }
598:
599: if (adoc != null) {
600: adoc.readLock();
601:
602: // Start listening for changes
603: if (!releaseOnly) {
604: lastDocument = adoc;
605: // Add document listener with specific priority
606: DocumentUtilities.addDocumentListener(lastDocument,
607: this , DocumentListenerPriority.FOLD_UPDATE);
608: }
609: }
610: try {
611: lock();
612: try {
613: rebuildManagers(releaseOnly);
614: } finally {
615: unlock();
616: }
617: } finally {
618: if (adoc != null) {
619: adoc.readUnlock();
620: }
621: }
622: }
623:
624: /**
625: * Rebuild (or release) the root folds of the hierarchy in the event dispatch thread.
626: *
627: * @param releaseOnly release the current root folds
628: * but make the new root folds array empty.
629: */
630: private void rebuildManagers(boolean releaseOnly) {
631: for (int i = 0; i < operations.length; i++) {
632: operations[i].release();
633: }
634: operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY; // really release
635:
636: // Call all the providers
637: FoldManagerFactoryProvider provider = !releaseOnly ? FoldManagerFactoryProvider
638: .getDefault()
639: : FoldManagerFactoryProvider.getEmpty();
640:
641: List factoryList = provider.getFactoryList(getHierarchy());
642: int factoryListLength = factoryList.size();
643:
644: if (debug) {
645: /*DEBUG*/System.err.println("FoldHierarchy rebuild():" // NOI18N
646: + " FoldManager factory count=" + factoryListLength // NOI18N
647: );
648: }
649:
650: // Create fold managers
651: int priority = factoryListLength - 1; // highest priority (till lowest == 0)
652: boolean ok = false;
653: try {
654: operations = new FoldOperationImpl[factoryListLength];
655: for (int i = 0; i < factoryListLength; i++) {
656: FoldManagerFactory factory = (FoldManagerFactory) factoryList
657: .get(i);
658: FoldManager manager = factory.createFoldManager();
659: operations[i] = new FoldOperationImpl(this , manager,
660: priority);
661: priority--;
662: }
663: ok = true;
664: } finally {
665: if (!ok) {
666: operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY;
667: }
668: }
669:
670: // Init managers under a local transaction
671: FoldHierarchyTransactionImpl transaction = openTransaction();
672: ok = false;
673: try {
674: // Remove all original folds - pass array of all blocked folds
675: Fold[] allBlocked = new Fold[blocked2block.size()];
676: blocked2block.keySet().toArray(allBlocked);
677: transaction.removeAllFolds(allBlocked);
678:
679: // Init folds in all fold managers
680: // Go from the manager with highest priority (index 0)
681: for (int i = 0; i < factoryListLength; i++) {
682: operations[i].initFolds(transaction);
683: }
684: ok = true; // inited successfully
685: } finally {
686: if (!ok) {
687: // TODO - remove folds under root fold
688: operations = EMPTY_FOLD_OPERTAION_IMPL_ARRAY;
689: }
690: transaction.commit();
691: }
692: }
693:
694: private void startComponentChangesListening() {
695: if (componentChangesListener == null) {
696: // Start listening on component changes
697: componentChangesListener = new PropertyChangeListener() {
698: public void propertyChange(PropertyChangeEvent evt) {
699: String propName = evt.getPropertyName();
700: if ("document".equals(propName)) { //NOI18N
701: foldingEnabled = getFoldingEnabledSetting();
702: rebuild();
703: } else if (PROPERTY_FOLDING_ENABLED
704: .equals(propName)) {
705: foldingEnabledSettingChange();
706: }
707: }
708: };
709:
710: // Start listening on the component.
711: // As the hierarchy instance is stored as a property of the component
712: // (and in fact the spi and the reference to the listener as well)
713: // the listener does not need to be removed
714: getComponent().addPropertyChangeListener(
715: componentChangesListener);
716: }
717: }
718:
719: public void insertUpdate(DocumentEvent evt) {
720: lock();
721: try {
722: FoldHierarchyTransactionImpl transaction = openTransaction();
723: try {
724: transaction.insertUpdate(evt);
725:
726: int operationsLength = operations.length;
727: for (int i = 0; i < operationsLength; i++) {
728: operations[i].insertUpdate(evt, transaction);
729: }
730: } finally {
731: transaction.commit();
732: }
733: } finally {
734: unlock();
735: }
736: }
737:
738: public void removeUpdate(DocumentEvent evt) {
739: lock();
740: try {
741: FoldHierarchyTransactionImpl transaction = openTransaction();
742: try {
743: transaction.removeUpdate(evt);
744:
745: int operationsLength = operations.length;
746: for (int i = 0; i < operationsLength; i++) {
747: operations[i].removeUpdate(evt, transaction);
748: }
749: } finally {
750: transaction.commit();
751: }
752: } finally {
753: unlock();
754: }
755: }
756:
757: public void changedUpdate(DocumentEvent evt) {
758: lock();
759: try {
760: FoldHierarchyTransactionImpl transaction = openTransaction();
761: try {
762: transaction.changedUpdate(evt);
763:
764: int operationsLength = operations.length;
765: for (int i = 0; i < operationsLength; i++) {
766: operations[i].changedUpdate(evt, transaction);
767: }
768: } finally {
769: transaction.commit();
770: }
771: } finally {
772: unlock();
773: }
774: }
775:
776: private boolean getFoldingEnabledSetting() {
777: Boolean b = (Boolean) component
778: .getClientProperty(PROPERTY_FOLDING_ENABLED);
779: return (b != null) ? b.booleanValue() : true;
780: }
781:
782: public void foldingEnabledSettingChange() {
783: boolean origFoldingEnabled = foldingEnabled;
784: foldingEnabled = getFoldingEnabledSetting();
785: if (origFoldingEnabled != foldingEnabled) {
786: SwingUtilities.invokeLater(new Runnable() {
787: public void run() {
788: rebuild();
789: }
790: });
791: }
792: }
793:
794: /**
795: * Check the internal consistency of the hierarchy
796: * and its contained folds. This is useful for testing purposes.
797: *
798: * @throws IllegalStateException in case an inconsistency is found.
799: */
800: public void checkConsistency() {
801: try {
802: checkFoldConsistency(getRootFold());
803: } catch (RuntimeException e) {
804: /*DEBUG*/System.err
805: .println("FOLD HIERARCHY INCONSISTENCY FOUND\n"
806: + this ); // NOI18N
807: throw e; // rethrow the exception
808: }
809: }
810:
811: private static void checkFoldConsistency(Fold fold) {
812: int startOffset = fold.getStartOffset();
813: int endOffset = fold.getEndOffset();
814: int lastEndOffset = startOffset;
815:
816: for (int i = 0; i < fold.getFoldCount(); i++) {
817: Fold child = fold.getFold(i);
818: if (child.getParent() != fold) {
819: throw new IllegalStateException(
820: "Wrong parent of child=" // NOI18N
821: + child + ": " + child.getParent() // NOI18N
822: + " != " + fold); // NOI18N
823: }
824: int foldIndex = fold.getFoldIndex(child);
825: if (foldIndex != i) {
826: throw new IllegalStateException("Fold index "
827: + foldIndex // NOI18N
828: + " instead of " + i); // NOI18N
829: }
830:
831: int childStartOffset = child.getStartOffset();
832: int childEndOffset = child.getEndOffset();
833: if (childStartOffset < lastEndOffset) {
834: throw new IllegalStateException("childStartOffset="
835: + childStartOffset // NOI18N
836: + " < lastEndOffset=" + lastEndOffset); // NOI18N
837: }
838: if (childStartOffset > childEndOffset) {
839: throw new IllegalStateException("childStartOffset="
840: + childStartOffset // NOI18N
841: + " > childEndOffset=" + childEndOffset); // NOI18N
842: }
843: lastEndOffset = childEndOffset;
844:
845: checkFoldConsistency(child);
846: }
847: }
848:
849: public String toString() {
850: StringBuffer sb = new StringBuffer();
851: sb.append("component="); // NOI18N
852: sb.append(System.identityHashCode(getComponent()));
853: sb.append('\n');
854:
855: // Append info about root folds
856: sb.append(FoldUtilitiesImpl.foldToStringChildren(hierarchy
857: .getRootFold(), 0));
858: sb.append('\n');
859:
860: // Append info about blocked folds
861: if (blocked2block != null) {
862: sb.append("BLOCKED\n"); // NOI18N
863: for (Iterator it = blocked2block.entrySet().iterator(); it
864: .hasNext();) {
865: Map.Entry entry = (Map.Entry) it.next();
866: sb.append(" "); // NOI18N
867: sb.append(entry.getKey());
868: sb.append(" blocked-by "); // NOI18N
869: sb.append(entry.getValue());
870: sb.append('\n');
871: }
872: }
873:
874: // Append info about blockers
875: if (block2blockedSet != null) {
876: sb.append("BLOCKERS\n"); // NOI18N
877: for (Iterator it = block2blockedSet.entrySet().iterator(); it
878: .hasNext();) {
879: Map.Entry entry = (Map.Entry) it.next();
880: sb.append(" "); // NOI18N
881: sb.append(entry.getKey());
882: sb.append('\n');
883: Set blockedSet = (Set) entry.getValue();
884: for (Iterator it2 = blockedSet.iterator(); it2
885: .hasNext();) {
886: sb.append(" blocks "); // NOI18N
887: sb.append(it2.next());
888: sb.append('\n');
889: }
890: }
891: }
892:
893: int operationsLength = operations.length;
894: if (operationsLength > 0) {
895: sb.append("Fold Managers\n"); // NOI18N
896: for (int i = 0; i < operationsLength; i++) {
897: sb.append("FOLD MANAGER ["); // NOI18N
898: sb.append(i);
899: sb.append("]:\n"); // NOI18N
900: sb.append(operations[i].getManager());
901: sb.append("\n"); // NOI18N
902: }
903: }
904:
905: return sb.toString();
906: }
907:
908: }
|