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.api.editor.fold;
043:
044: import java.util.Collection;
045: import java.util.Collections;
046: import javax.swing.event.DocumentEvent;
047: import javax.swing.text.BadLocationException;
048: import javax.swing.text.Document;
049: import javax.swing.text.JTextComponent;
050: import org.netbeans.modules.editor.fold.ApiPackageAccessor;
051: import org.netbeans.modules.editor.fold.FoldHierarchyExecution;
052: import org.netbeans.modules.editor.fold.FoldOperationImpl;
053:
054: /**
055: * Hierarchy of the folds for a single text component represents
056: * a model of the code-folding.
057: *
058: * <br>
059: * It is the main entry point into the Code Folding API.
060: * <br>
061: * Its instance can be obtained by {@link #get(javax.swing.text.JTextComponent)}.
062: * <br>
063: * The hierarhcy mainly provides access to the root fold
064: * by {@link #getRootFold()}
065: * and allows to expand/collapse the folds
066: * and listen for fold events describing folds structure changes
067: * and state changes of any of the folds in the hierarchy.
068: *
069: * <p>
070: * Hierarchy is logically bound to view
071: * i.e. {@link javax.swing.text.JTextComponent}
072: * instead of the document model because
073: * if there would be two views over the same document
074: * then a particular fold can be collapsed in one view
075: * but uncollapsed in another.
076: * <br>
077: * It's up to the concrete fold implementations to possibly share
078: * some common information even on document model level
079: * e.g. java-related folds in multiple views over
080: * a single java source document can share
081: * the document-level parsing information.
082: * <br>
083: * On the other hand user-defined folds (e.g. by collapsing caret selection)
084: * will only be held for the component in which they were created.
085: *
086: * <p>
087: * Only one thread at the time can access the code folding hierarchy.
088: * Prior working with the hierarchy a document-level lock
089: * must be obtained first followed by call to {@link #render(Runnable)}
090: * (or {@link #lock()} for advanced uses) which ensure that the hierarchy
091: * gets locked exclusively.
092: * <br>
093: * The document lock can be either readlock
094: * e.g. by using {@link javax.swing.text.Document#render(Runnable)}
095: * or writelock
096: * e.g. when in {@link javax.swing.event.DocumentListener})
097: * and must be obtained on component's document
098: * i.e. {@link javax.swing.text.JTextComponent#getDocument()}
099: * should be used.
100: *
101: * <p>
102: * The whole fold hierarchy related code expects that the document
103: * instances of the text component will subclass
104: * <code>javax.swing.text.AbstractDocument</code>.
105: *
106: * @author Miloslav Metelka
107: * @version 1.00
108: */
109:
110: public final class FoldHierarchy {
111:
112: /**
113: * Fold type for the root fold.
114: */
115: public static final FoldType ROOT_FOLD_TYPE = new FoldType(
116: "root-fold"); // NOI18N
117:
118: private static boolean apiPackageAccessorRegistered;
119:
120: static {
121: ensureApiAccessorRegistered();
122: }
123:
124: private static void ensureApiAccessorRegistered() {
125: if (!apiPackageAccessorRegistered) {
126: apiPackageAccessorRegistered = true;
127: ApiPackageAccessor.register(new ApiPackageAccessorImpl());
128: }
129: }
130:
131: /**
132: * Execution carries out most of the fold hierarchy's functionality.
133: */
134: private FoldHierarchyExecution execution;
135:
136: /**
137: * Get the fold hierarchy for the given component. If the hierarchy
138: * does not exist yet it will get created.
139: * <br>
140: * The hierarchy will exist for the entire lifetime of the component.
141: * It is maintained as a client property of it.
142: *
143: * @return non-null fold hierarchy for the component.
144: */
145: public static synchronized FoldHierarchy get(
146: JTextComponent component) {
147: return FoldHierarchyExecution
148: .getOrCreateFoldHierarchy(component);
149: }
150:
151: /** Only instances created internally are allowed. */
152: private FoldHierarchy(FoldHierarchyExecution execution) {
153: // Synced under FoldHierarchy.class lock
154: this .execution = execution;
155: }
156:
157: /**
158: * Execute the given runnable over the exclusively locked hierarchy.
159: * <br>
160: * Prior using this method the document must be locked.
161: * The document lock can be either readlock
162: * e.g. by using {@link javax.swing.text.Document#render(Runnable)}
163: * or writelock
164: * e.g. when in {@link javax.swing.event.DocumentListener})
165: * and must be obtained on component's document
166: * i.e. {@link javax.swing.text.JTextComponent#getDocument()}
167: * should be used.
168: *
169: * @param r the runnable to be executed.
170: */
171: public void render(Runnable r) {
172: lock();
173: try {
174: r.run();
175: } finally {
176: unlock();
177: }
178: }
179:
180: /**
181: * Lock the hierarchy for exclusive use. This method must only
182: * be used together with {@link #unlock()} in <code>try..finally</code> block.
183: * <br>
184: * Prior using this method the document must be locked.
185: * The document lock can be either readlock
186: * e.g. by using {@link javax.swing.text.Document#render(Runnable)}
187: * or writelock
188: * e.g. when in {@link javax.swing.event.DocumentListener})
189: * and must be obtained on component's document
190: * i.e. {@link javax.swing.text.JTextComponent#getDocument()}
191: * should be used.
192: *
193: * <p>
194: * <font color="red">
195: * <b>Note:</b> The clients using this method must ensure that
196: * they <b>always</b> use this method in the following pattern:<pre>
197: *
198: * lock();
199: * try {
200: * ...
201: * } finally {
202: * unlock();
203: * }
204: * </pre>
205: * </font>
206: *
207: * @see #render(Runnable)
208: */
209: public void lock() {
210: execution.lock();
211: }
212:
213: /**
214: * Unlock the hierarchy from exclusive use. This method must only
215: * be used together with {@link #lock()} in <code>try..finally</code> block.
216: */
217: public void unlock() {
218: execution.unlock();
219: }
220:
221: /**
222: * Collapse the given fold.
223: * <br>
224: * Nothing is done if the fold is already collapsed.
225: *
226: * <p>
227: * <b>Note:</b> The hierarchy must be locked prior using of this method.
228: *
229: * @param f fold to be collapsed.
230: */
231: public void collapse(Fold f) {
232: collapse(Collections.singletonList(f));
233: }
234:
235: /**
236: * Collapse all the folds contained in the given collection.
237: *
238: * <p>
239: * <b>Note:</b> The hierarchy must be locked prior using of this method.
240: *
241: * @param c collection of the {@link Fold}s to be collapsed. The folds
242: * must be present in this hierarchy.
243: */
244: public void collapse(Collection c) {
245: execution.collapse(c);
246: }
247:
248: /**
249: * Expand the given fold.
250: * <br>
251: * Nothing is done if the fold is already expanded.
252: *
253: * <p>
254: * <b>Note:</b> The hierarchy must be locked prior using of this method.
255: *
256: * @param f fold to be expanded.
257: */
258: public void expand(Fold f) {
259: expand(Collections.singletonList(f));
260: }
261:
262: /**
263: * Expand all the folds contained in the given collection.
264: *
265: * <p>
266: * <b>Note:</b> The hierarchy must be locked prior using of this method.
267: *
268: * @param c collection of the {@link Fold}s to be collapsed. The folds
269: * must be present in this hierarchy.
270: */
271: public void expand(Collection c) {
272: execution.expand(c);
273: }
274:
275: /**
276: * Collapse the given fold if it's expanded and expand it if it's
277: * collapsed.
278: *
279: * <p>
280: * <b>Note:</b> The hierarchy must be locked prior using of this method.
281: *
282: * @param f fold which state should be toggled.
283: */
284: public void toggle(Fold f) {
285: if (f.isCollapsed()) {
286: expand(f);
287: } else { // expanded
288: collapse(f);
289: }
290: }
291:
292: /**
293: * Get the text component for which this fold hierarchy was created.
294: *
295: * @return non-null text component for which this fold hierarchy was created.
296: */
297: public JTextComponent getComponent() {
298: return execution.getComponent();
299: }
300:
301: /**
302: * Get the root fold of this hierarchy.
303: *
304: * @return root fold of this hierarchy.
305: * The root fold covers the whole document and is uncollapsable.
306: */
307: public Fold getRootFold() {
308: return execution.getRootFold();
309: }
310:
311: /**
312: * Add listener for changes done in the hierarchy.
313: *
314: * @param l non-null listener to be added.
315: */
316: public void addFoldHierarchyListener(FoldHierarchyListener l) {
317: execution.addFoldHierarchyListener(l);
318: }
319:
320: /**
321: * Remove previously added listener for changes done in the hierarchy.
322: *
323: * @param l non-null listener to be removed.
324: */
325: public void removeFoldHierarchyListener(FoldHierarchyListener l) {
326: execution.removeFoldHierarchyListener(l);
327: }
328:
329: /**
330: * Get a string description of the hierarchy for debugging purposes.
331: * <br>
332: * Like all other methods this one can only be used under locking
333: * conditions for the hierarchy.
334: */
335: public String toString() {
336: return execution.toString();
337: }
338:
339: /**
340: * Implementation of the API package accessor allows the implementation
341: * to access certain package-private methods from the api classes.
342: */
343: private static final class ApiPackageAccessorImpl extends
344: ApiPackageAccessor {
345:
346: public FoldHierarchy createFoldHierarchy(
347: FoldHierarchyExecution execution) {
348: return new FoldHierarchy(execution);
349: }
350:
351: public Fold createFold(FoldOperationImpl operation,
352: FoldType type, String description, boolean collapsed,
353: Document doc, int startOffset, int endOffset,
354: int startGuardedLength, int endGuardedLength,
355: Object extraInfo) throws BadLocationException {
356: return new Fold(operation, type, description, collapsed,
357: doc, startOffset, endOffset, startGuardedLength,
358: endGuardedLength, extraInfo);
359: }
360:
361: public FoldHierarchyEvent createFoldHierarchyEvent(
362: FoldHierarchy source, Fold[] removedFolds,
363: Fold[] addedFolds, FoldStateChange[] foldStateChanges,
364: int affectedStartOffset, int affectedEndOffset) {
365: return new FoldHierarchyEvent(source, removedFolds,
366: addedFolds, foldStateChanges, affectedStartOffset,
367: affectedEndOffset);
368: }
369:
370: public FoldStateChange createFoldStateChange(Fold fold) {
371: return new FoldStateChange(fold);
372: }
373:
374: public void foldSetParent(Fold fold, Fold parent) {
375: fold.setParent(parent);
376: }
377:
378: public void foldExtractToChildren(Fold fold, int index,
379: int length, Fold targetFold) {
380: fold.extractToChildren(index, length, targetFold);
381: }
382:
383: public Fold foldReplaceByChildren(Fold fold, int index) {
384: return fold.replaceByChildren(index);
385: }
386:
387: public void foldSetCollapsed(Fold fold, boolean collapsed) {
388: fold.setCollapsed(collapsed);
389: }
390:
391: public void foldSetDescription(Fold fold, String description) {
392: fold.setDescription(description);
393: }
394:
395: public void foldSetStartOffset(Fold fold, Document doc,
396: int startOffset) throws BadLocationException {
397: fold.setStartOffset(doc, startOffset);
398: }
399:
400: public void foldSetEndOffset(Fold fold, Document doc,
401: int endOffset) throws BadLocationException {
402: fold.setEndOffset(doc, endOffset);
403: }
404:
405: public boolean foldIsStartDamaged(Fold fold) {
406: return fold.isStartDamaged();
407: }
408:
409: public boolean foldIsEndDamaged(Fold fold) {
410: return fold.isEndDamaged();
411: }
412:
413: public boolean foldIsExpandNecessary(Fold fold) {
414: return fold.isExpandNecessary();
415: }
416:
417: public void foldInsertUpdate(Fold fold, DocumentEvent evt) {
418: fold.insertUpdate(evt);
419: }
420:
421: public void foldRemoveUpdate(Fold fold, DocumentEvent evt) {
422: fold.removeUpdate(evt);
423: }
424:
425: public FoldOperationImpl foldGetOperation(Fold fold) {
426: return fold.getOperation();
427: }
428:
429: public int foldGetRawIndex(Fold fold) {
430: return fold.getRawIndex();
431: }
432:
433: public void foldSetRawIndex(Fold fold, int rawIndex) {
434: fold.setRawIndex(rawIndex);
435: }
436:
437: public void foldUpdateRawIndex(Fold fold, int rawIndexDelta) {
438: fold.updateRawIndex(rawIndexDelta);
439: }
440:
441: public Object foldGetExtraInfo(Fold fold) {
442: return fold.getExtraInfo();
443: }
444:
445: public void foldStateChangeCollapsedChanged(FoldStateChange fsc) {
446: fsc.collapsedChanged();
447: }
448:
449: public void foldStateChangeDescriptionChanged(
450: FoldStateChange fsc) {
451: fsc.descriptionChanged();
452: }
453:
454: public void foldStateChangeStartOffsetChanged(
455: FoldStateChange fsc, int originalStartOffset) {
456: fsc.startOffsetChanged(originalStartOffset);
457: }
458:
459: public void foldStateChangeEndOffsetChanged(
460: FoldStateChange fsc, int originalEndOffset) {
461: fsc.endOffsetChanged(originalEndOffset);
462: }
463:
464: }
465:
466: }
|