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.util.ArrayList;
045: import java.util.Arrays;
046: import java.util.Collection;
047: import java.util.Comparator;
048: import java.util.Iterator;
049: import java.util.List;
050: import javax.swing.text.AbstractDocument;
051: import org.netbeans.api.editor.fold.Fold;
052: import org.netbeans.api.editor.fold.FoldHierarchy;
053: import org.netbeans.api.editor.fold.FoldHierarchyEvent;
054: import org.netbeans.api.editor.fold.FoldStateChange;
055: import org.netbeans.api.editor.fold.FoldUtilities;
056:
057: /**
058: * Implementations of methods from {@link org.netbeans.api.editor.fold.FoldUtilities}.
059: *
060: * @author Miloslav Metelka
061: * @version 1.00
062: */
063:
064: public final class FoldUtilitiesImpl {
065:
066: private FoldUtilitiesImpl() {
067: // No instances
068: }
069:
070: public static void collapseOrExpand(FoldHierarchy hierarchy,
071: Collection foldTypes, boolean collapse) {
072:
073: AbstractDocument adoc = (AbstractDocument) hierarchy
074: .getComponent().getDocument();
075: adoc.readLock();
076: try {
077: hierarchy.lock();
078: try {
079: List foldList = findRecursive(null, hierarchy
080: .getRootFold(), foldTypes);
081: if (collapse) {
082: hierarchy.collapse(foldList);
083: } else {
084: hierarchy.expand(foldList);
085: }
086: } finally {
087: hierarchy.unlock();
088: }
089: } finally {
090: adoc.readUnlock();
091: }
092: }
093:
094: public static int findFoldStartIndex(Fold fold, int offset,
095: boolean first) {
096: int foldCount = fold.getFoldCount();
097: int low = 0;
098: int high = foldCount - 1;
099:
100: while (low <= high) {
101: int mid = (low + high) / 2;
102: Fold midFold = fold.getFold(mid);
103: int midFoldStartOffset = midFold.getStartOffset();
104:
105: if (midFoldStartOffset < offset) {
106: low = mid + 1;
107: } else if (midFoldStartOffset > offset) {
108: high = mid - 1;
109: } else {
110: // fold starting exactly at the given offset found
111: if (first) { // search for first fold
112: mid--;
113: while (mid >= 0
114: && fold.getFold(mid).getStartOffset() == offset) {
115: mid--;
116: }
117: mid++;
118:
119: } else { // search for last fold
120: mid++;
121: // Search for fold with startOffset greater than offset
122: while (mid < foldCount
123: && fold.getFold(mid).getStartOffset() == offset) {
124: mid++;
125: }
126: mid--;
127: }
128: return mid;
129: }
130: }
131: return high;
132: }
133:
134: /**
135: * Find a hint index of where a child fold should be inserted in its parent.
136: *
137: * @param fold fold into which the child fold should be inserted.
138: * @param childStartOffset starting offset of the child to be inserted.
139: * @return hint index at which the child fold should be inserted.
140: * <br>
141: * The client must additionally check whether the end offset
142: * of the preceding child fold does not overlap with the given child fold
143: * and if so then either remove the clashing fold or stop inserting
144: * the child fold.
145: * <br>
146: * The client must also check whether ending offset of the given child fold
147: * does not overlap with the starting offset of the following child fold.
148: */
149: public static int findFoldInsertIndex(Fold fold,
150: int childStartOffset) {
151: return findFoldStartIndex(fold, childStartOffset, false) + 1;
152: }
153:
154: public static int findFoldEndIndex(Fold fold, int offset) {
155: int foldCount = fold.getFoldCount();
156: int low = 0;
157: int high = foldCount - 1;
158:
159: while (low <= high) {
160: int mid = (low + high) / 2;
161: Fold midFold = fold.getFold(mid);
162: int midFoldEndOffset = midFold.getEndOffset();
163:
164: if (midFoldEndOffset < offset) {
165: low = mid + 1;
166: } else if (midFoldEndOffset > offset) {
167: high = mid - 1;
168: } else {
169: // fold ending exactly at the given offset found => move to next one above
170: mid++;
171: while (mid < foldCount
172: && fold.getFold(mid).getEndOffset() <= offset) {
173: mid++;
174: }
175: return mid;
176: }
177: }
178: return low;
179: }
180:
181: public static List childrenAsList(Fold fold, int index, int count) {
182: List l = new ArrayList(count);
183: while (--count >= 0) {
184: l.add(fold.getFold(index));
185: index++;
186: }
187: return l;
188: }
189:
190: public static List find(Fold fold, Collection foldTypes) {
191: List l = new ArrayList();
192: int foldCount = fold.getFoldCount();
193: for (int i = 0; i < foldCount; i++) {
194: Fold child = fold.getFold(i);
195: if (foldTypes == null
196: || foldTypes.contains(child.getType())) {
197: l.add(child);
198: }
199: }
200: return l;
201: }
202:
203: public static List findRecursive(List l, Fold fold,
204: Collection foldTypes) {
205: if (l == null) {
206: l = new ArrayList();
207: }
208:
209: int foldCount = fold.getFoldCount();
210: for (int i = 0; i < foldCount; i++) {
211: Fold child = fold.getFold(i);
212: if (foldTypes == null
213: || foldTypes.contains(child.getType())) {
214: l.add(child);
215: }
216: findRecursive(l, child, foldTypes);
217: }
218: return l;
219:
220: }
221:
222: /** Returns the fold at the specified offset. Returns null in case of root fold */
223: public static Fold findOffsetFold(FoldHierarchy hierarchy,
224: int offset) {
225: int distance = Integer.MAX_VALUE;
226: Fold rootFold = hierarchy.getRootFold();
227: Fold fold = rootFold;
228:
229: boolean inspectNested = true;
230: while (inspectNested) {
231: int childIndex = findFoldStartIndex(fold, offset, false);
232: if (childIndex >= 0) {
233: Fold wrapFold = fold.getFold(childIndex);
234: int startOffset = wrapFold.getStartOffset();
235: int endOffset = wrapFold.getEndOffset();
236: // This is not like containsOffset() because of "<= endOffset"
237: if (startOffset <= offset && offset <= endOffset) {
238: fold = wrapFold;
239: } else {
240: inspectNested = false;
241: }
242: } else { // no children => break
243: inspectNested = false;
244: }
245: }
246: return (fold != rootFold) ? fold : null;
247: }
248:
249: public static Fold findNearestFold(FoldHierarchy hierarchy,
250: int offset, int endOffset) {
251: Fold nearestFold = null;
252: int distance = Integer.MAX_VALUE;
253: Fold fold = hierarchy.getRootFold();
254:
255: boolean inspectNested = true;
256: while (inspectNested) {
257: int childCount = fold.getFoldCount();
258: int childIndex = findFoldEndIndex(fold, offset);
259: if (childIndex < childCount) {
260: Fold wrapOrAfterFold = fold.getFold(childIndex);
261: int startOffset = wrapOrAfterFold.getStartOffset();
262: if (startOffset >= endOffset) { // starts at or after endOffset
263: break;
264: }
265:
266: Fold afterFold; // fold after the offset
267: if (startOffset < offset) { // starts below offset
268: childIndex++;
269: afterFold = (childIndex < childCount) ? fold
270: .getFold(childIndex) : null;
271: // leave inspectNested to be true and prepare fold variable
272: fold = wrapOrAfterFold;
273:
274: } else { // starts above offset
275: afterFold = wrapOrAfterFold;
276: inspectNested = false;
277: }
278:
279: // Check whether the afterFold is the nearest
280: if (afterFold != null) {
281: int afterFoldDistance = afterFold.getStartOffset()
282: - offset;
283: if (afterFoldDistance < distance) {
284: distance = afterFoldDistance;
285: nearestFold = afterFold;
286: }
287: }
288:
289: } else { // no children => break
290: inspectNested = false;
291: }
292: }
293:
294: return nearestFold;
295: }
296:
297: public static Fold findFirstCollapsedFold(FoldHierarchy hierarchy,
298: int startOffset, int endOffset) {
299:
300: Fold fold = hierarchy.getRootFold();
301: Fold lastFold = null;
302: int lastIndex = 0;
303: while (true) {
304: // Find fold covering the startOffset
305: int index = findFoldEndIndex(fold, startOffset);
306: if (index >= fold.getFoldCount()) {
307: if (lastFold != null) {
308: return findCollapsedRec(lastFold, lastIndex + 1,
309: endOffset);
310: } else { // root level - no satisfying folds
311: return null;
312: }
313:
314: } else { // fold index within bounds
315: Fold childFold = fold.getFold(index);
316: if (childFold.isCollapsed()) { // return it if it's collapsed
317: return childFold;
318: }
319:
320: if (childFold.getStartOffset() >= startOffset) { // do not nest
321: return findCollapsedRec(fold, index, endOffset);
322: } else { // need to inspect children
323: lastFold = fold;
324: lastIndex = index;
325: fold = childFold;
326: }
327: }
328: }
329: }
330:
331: public static Iterator collapsedFoldIterator(
332: FoldHierarchy hierarchy, int startOffset, int endOffset) {
333: return new CollapsedFoldIterator(findFirstCollapsedFold(
334: hierarchy, startOffset, endOffset), endOffset);
335: }
336:
337: private static final class CollapsedFoldIterator implements
338: Iterator {
339:
340: private Fold nextFold;
341:
342: private int endOffset;
343:
344: public CollapsedFoldIterator(Fold nextFold, int endOffset) {
345: this .nextFold = nextFold;
346: this .endOffset = endOffset;
347: }
348:
349: public boolean hasNext() {
350: return (nextFold != null);
351: }
352:
353: public Object next() {
354: Fold result = nextFold;
355: nextFold = findNextCollapsedFold(nextFold, endOffset);
356: return result;
357: }
358:
359: public void remove() {
360: throw new UnsupportedOperationException();
361: }
362:
363: }
364:
365: public static Fold findNextCollapsedFold(Fold fold, int endOffset) {
366: if (FoldUtilities.isRootFold(fold)) { // start from the begining
367: return findCollapsedRec(fold, 0, endOffset);
368:
369: } else { // continue from valid fold
370: Fold parent = fold.getParent();
371: return findCollapsedRec(parent,
372: parent.getFoldIndex(fold) + 1, endOffset);
373: }
374: }
375:
376: private static Fold findCollapsedRec(Fold fold, int startIndex,
377: int endOffset) {
378: return findCollapsedRec(fold, startIndex, endOffset, true);
379: }
380:
381: private static Fold findCollapsedRec(Fold fold, int startIndex,
382: int endOffset, boolean findInUpperLevel) {
383:
384: if (fold.getStartOffset() > endOffset) {
385: return null;
386: }
387:
388: int foldCount = fold.getFoldCount();
389: while (startIndex < foldCount) {
390: Fold child = fold.getFold(startIndex);
391: if (child.isCollapsed()) {
392: return child;
393: } else {
394: Fold maybeCollapsed = findCollapsedRec(child, 0,
395: endOffset, false);
396: if (maybeCollapsed != null) {
397: return maybeCollapsed;
398: }
399: }
400: startIndex++;
401: }
402:
403: // No child was found collapsed -> go one level up
404: if (FoldUtilities.isRootFold(fold) || !findInUpperLevel) {
405: return null;
406: } else { // not root fold
407: Fold parent = fold.getParent();
408: return findCollapsedRec(parent,
409: parent.getFoldIndex(fold) + 1, endOffset, true);
410: }
411: }
412:
413: public static String foldToString(Fold fold) {
414: return "["
415: + fold.getType()
416: + "] " // NOI18N
417: + (fold.isCollapsed() ? "C" : "E")// NOI18N
418: + (FoldUtilities.isRootFold(fold) ? "" : Integer
419: .toString(ApiPackageAccessor.get()
420: .foldGetOperation(fold).getPriority()))
421: + " <"
422: + fold.getStartOffset() // NOI18N
423: + ","
424: + fold.getEndOffset()
425: + ">" // NOI18N
426: + (FoldUtilities.isRootFold(fold) ? "" : (", desc='"
427: + fold.getDescription() + "'")); // NOI18N
428: }
429:
430: public static void appendSpaces(StringBuffer sb, int spaces) {
431: while (--spaces >= 0) {
432: sb.append(' ');
433: }
434: }
435:
436: public static String foldToStringChildren(Fold fold, int indent) {
437: indent += 4;
438: StringBuffer sb = new StringBuffer();
439: sb.append(fold);
440: sb.append('\n');
441: int foldCount = fold.getFoldCount();
442: for (int i = 0; i < foldCount; i++) {
443: appendSpaces(sb, indent);
444: sb.append('[');
445: sb.append(i);
446: sb.append("]: "); // NOI18N
447: sb.append(foldToStringChildren(fold.getFold(i), indent));
448: }
449:
450: return sb.toString();
451: }
452:
453: public static String foldHierarchyEventToString(
454: FoldHierarchyEvent evt) {
455: StringBuffer sb = new StringBuffer();
456: int removedFoldCount = evt.getRemovedFoldCount();
457: for (int i = 0; i < removedFoldCount; i++) {
458: sb.append("R["); // NOI18N
459: sb.append(i);
460: sb.append("]: "); // NOI18N
461: sb.append(evt.getRemovedFold(i));
462: sb.append('\n');
463: }
464:
465: int addedFoldCount = evt.getAddedFoldCount();
466: for (int i = 0; i < addedFoldCount; i++) {
467: sb.append("A["); // NOI18N
468: sb.append(i);
469: sb.append("]: "); // NOI18N
470: sb.append(evt.getAddedFold(i));
471: sb.append('\n');
472: }
473:
474: int foldStateChangeCount = evt.getFoldStateChangeCount();
475: for (int i = 0; i < foldStateChangeCount; i++) {
476: FoldStateChange change = evt.getFoldStateChange(i);
477: sb.append("SC["); // NOI18N
478: sb.append(i);
479: sb.append("]: "); // NOI18N
480: sb.append(change);
481: sb.append('\n');
482: }
483: if (foldStateChangeCount == 0) {
484: sb.append("No FoldStateChange\n"); // NOI18N
485: }
486:
487: sb.append("affected: <"); // NOI18N
488: sb.append(evt.getAffectedStartOffset());
489: sb.append(","); // NOI18N
490: sb.append(evt.getAffectedEndOffset());
491: sb.append(">\n"); // NOI18N
492:
493: return sb.toString();
494: }
495:
496: public static String foldStateChangeToString(FoldStateChange change) {
497: StringBuffer sb = new StringBuffer();
498: if (change.isCollapsedChanged()) {
499: sb.append("C"); // NOI18N
500: }
501: if (change.isDescriptionChanged()) {
502: sb.append("D"); // NOI18N
503: }
504: if (change.isEndOffsetChanged()) {
505: sb.append("E"); // NOI18N
506: }
507: sb.append(" fold="); // NOI18N
508: sb.append(change.getFold());
509: return sb.toString();
510: }
511:
512: }
|