KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > CustomFoldManager


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.editor;
21
22 import org.netbeans.lib.editor.util.swing.DocumentUtilities;
23 import org.openide.ErrorManager;
24
25 import javax.swing.text.Document JavaDoc;
26 import javax.swing.text.BadLocationException JavaDoc;
27 import javax.swing.text.Position JavaDoc;
28 import javax.swing.event.DocumentEvent JavaDoc;
29 import java.util.*;
30 import java.util.regex.Pattern JavaDoc;
31 import java.util.regex.Matcher JavaDoc;
32 import org.netbeans.api.editor.fold.Fold;
33 import org.netbeans.api.editor.fold.FoldType;
34 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
35 import org.netbeans.spi.editor.fold.FoldManager;
36 import org.netbeans.spi.editor.fold.FoldManagerFactory;
37 import org.netbeans.spi.editor.fold.FoldOperation;
38
39 /**
40  * Fold maintainer that creates and updates custom folds.
41  *
42  * @author Dusan Balek, Miloslav Metelka
43  * @version 1.00
44  */

45
46 final class CustomFoldManager implements FoldManager {
47     
48     private static final boolean debug = false;
49     
50     public static final FoldType CUSTOM_FOLD_TYPE = new FoldType("custom-fold"); // NOI18N
51

52     private FoldOperation operation;
53     private Document JavaDoc doc;
54     private org.netbeans.editor.GapObjectArray markArray = new org.netbeans.editor.GapObjectArray();
55     private int minUpdateMarkOffset;
56     private int maxUpdateMarkOffset;
57     private List removedFoldList;
58     private HashMap customFoldId = new HashMap();
59     
60     public void init(FoldOperation operation) {
61         this.operation = operation;
62     }
63     
64     private FoldOperation getOperation() {
65         return operation;
66     }
67
68     public void initFolds(FoldHierarchyTransaction transaction) {
69         try {
70             doc = getOperation().getHierarchy().getComponent().getDocument();
71             updateFolds(SyntaxUpdateTokens.getTokenInfoList(doc), transaction);
72         } catch (BadLocationException JavaDoc e) {
73             ErrorManager.getDefault().notify(e);
74         }
75     }
76
77     public void insertUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
78         try {
79             processRemovedFolds(transaction);
80             updateFolds(SyntaxUpdateTokens.getTokenInfoList(evt), transaction);
81         } catch (BadLocationException JavaDoc e) {
82             ErrorManager.getDefault().notify(e);
83         }
84     }
85
86     public void removeUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
87         try {
88             processRemovedFolds(transaction);
89             removeAffectedMarks(evt, transaction);
90             updateFolds(SyntaxUpdateTokens.getTokenInfoList(evt), transaction);
91         } catch (BadLocationException JavaDoc e) {
92             ErrorManager.getDefault().notify(e);
93         }
94     }
95     
96     public void changedUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
97     }
98     
99     public void removeEmptyNotify(Fold emptyFold) {
100         removeFoldNotify(emptyFold);
101     }
102     
103     public void removeDamagedNotify(Fold damagedFold) {
104         removeFoldNotify(damagedFold);
105     }
106     
107     public void expandNotify(Fold expandedFold) {
108         
109     }
110
111     public void release() {
112
113     }
114     
115     private void removeFoldNotify(Fold removedFold) {
116         if (removedFoldList == null) {
117             removedFoldList = new ArrayList(3);
118         }
119         removedFoldList.add(removedFold);
120     }
121     
122     private void removeAffectedMarks(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
123         int removeOffset = evt.getOffset();
124         int markIndex = findMarkIndex(removeOffset);
125         if (markIndex < getMarkCount()) {
126             FoldMarkInfo mark;
127             while (markIndex >= 0 && (mark = getMark(markIndex)).getOffset() == removeOffset) {
128                 mark.release(false, transaction);
129                 removeMark(markIndex);
130                 markIndex--;
131             }
132         }
133     }
134     
135     private void processRemovedFolds(FoldHierarchyTransaction transaction) {
136         if (removedFoldList != null) {
137             for (int i = removedFoldList.size() - 1; i >= 0; i--) {
138                 Fold removedFold = (Fold)removedFoldList.get(i);
139                 FoldMarkInfo startMark = (FoldMarkInfo)getOperation().getExtraInfo(removedFold);
140                 if (startMark.getId() != null)
141                     customFoldId.put(startMark.getId(), Boolean.valueOf(removedFold.isCollapsed())); // remember the last fold's state before remove
142
FoldMarkInfo endMark = startMark.getPairMark(); // get prior releasing
143
if (getOperation().isStartDamaged(removedFold)) { // start mark area was damaged
144
startMark.release(true, transaction); // forced remove
145
}
146                 if (getOperation().isEndDamaged(removedFold)) {
147                     endMark.release(true, transaction);
148                 }
149             }
150         }
151         removedFoldList = null;
152     }
153
154     private void markUpdate(FoldMarkInfo mark) {
155         markUpdate(mark.getOffset());
156     }
157     
158     private void markUpdate(int offset) {
159         if (offset < minUpdateMarkOffset) {
160             minUpdateMarkOffset = offset;
161         }
162         if (offset > maxUpdateMarkOffset) {
163             maxUpdateMarkOffset = offset;
164         }
165     }
166     
167     private FoldMarkInfo getMark(int index) {
168         return (FoldMarkInfo)markArray.getItem(index);
169     }
170     
171     private int getMarkCount() {
172         return markArray.getItemCount();
173     }
174     
175     private void removeMark(int index) {
176         if (debug) {
177             /*DEBUG*/System.err.println("Removing mark from ind=" + index // NOI18N
178
+ ": " + getMark(index)); // NOI18N
179
}
180         markArray.remove(index, 1);
181     }
182     
183     private void insertMark(int index, FoldMarkInfo mark) {
184         markArray.insertItem(index, mark);
185         if (debug) {
186             /*DEBUG*/System.err.println("Inserted mark at ind=" + index // NOI18N
187
+ ": " + mark); // NOI18N
188
}
189     }
190
191     private int findMarkIndex(int offset) {
192         int markCount = getMarkCount();
193         int low = 0;
194         int high = markCount - 1;
195         
196         while (low <= high) {
197             int mid = (low + high) / 2;
198             int midMarkOffset = getMark(mid).getOffset();
199             
200             if (midMarkOffset < offset) {
201                 low = mid + 1;
202             } else if (midMarkOffset > offset) {
203                 high = mid - 1;
204             } else {
205                 // mark starting exactly at the given offset found
206
// If multiple -> find the one with highest index
207
mid++;
208                 while (mid < markCount && getMark(mid).getOffset() == offset) {
209                     mid++;
210                 }
211                 mid--;
212                 return mid;
213             }
214         }
215         return low; // return higher index (e.g. for insert)
216
}
217     
218     private List getMarkList(List tokenList) {
219         List markList = null;
220         int tokenListSize = tokenList.size();
221         if (tokenListSize != 0) {
222             for (int i = 0; i < tokenListSize; i++) {
223                 SyntaxUpdateTokens.TokenInfo tokenInfo = (SyntaxUpdateTokens.TokenInfo) tokenList.get(i);
224                 FoldMarkInfo info;
225                 try {
226                     info = scanToken(tokenInfo);
227                 } catch (BadLocationException JavaDoc e) {
228                     ErrorManager.getDefault().notify(e);
229                     info = null;
230                 }
231
232                 if (info != null) {
233                     if (markList == null) {
234                         markList = new ArrayList();
235                     }
236                     markList.add(info);
237                 }
238             }
239         }
240         return markList;
241     }
242     
243     private void processTokenList(List tokenList, FoldHierarchyTransaction transaction) {
244         List markList = getMarkList(tokenList);
245         int markListSize;
246         if (markList != null && ((markListSize = markList.size()) > 0)) {
247             // Find the index for insertion
248
int offset = ((FoldMarkInfo)markList.get(0)).getOffset();
249             int arrayMarkIndex = findMarkIndex(offset);
250             // Remember the corresponding mark in the array as well
251
FoldMarkInfo arrayMark;
252             int arrayMarkOffset;
253             if (arrayMarkIndex < getMarkCount()) {
254                 arrayMark = getMark(arrayMarkIndex);
255                 arrayMarkOffset = arrayMark.getOffset();
256             } else { // at last mark
257
arrayMark = null;
258                 arrayMarkOffset = Integer.MAX_VALUE;
259             }
260
261             for (int i = 0; i < markListSize; i++) {
262                 FoldMarkInfo listMark = (FoldMarkInfo)markList.get(i);
263                 int listMarkOffset = listMark.getOffset();
264                 if (i == 0 || i == markListSize - 1) {
265                     // Update the update-offsets by the first and last marks in the list
266
markUpdate(listMarkOffset);
267                 }
268                 if (listMarkOffset >= arrayMarkOffset) {
269                     if (listMarkOffset == arrayMarkOffset) {
270                         // At the same offset - likely the same mark
271
// -> retain the collapsed state
272
listMark.setCollapsed(arrayMark.isCollapsed());
273                     }
274                     if (!arrayMark.isReleased()) { // make sure that the mark is released
275
arrayMark.release(false, transaction);
276                     }
277                     removeMark(arrayMarkIndex);
278                     if (debug) {
279                         /*DEBUG*/System.err.println("Removed dup mark from ind="
280                             + arrayMarkIndex + ": " + arrayMark); // NOI18N
281
}
282                     if (arrayMarkIndex < getMarkCount()) {
283                         arrayMark = getMark(arrayMarkIndex);
284                         arrayMarkOffset = arrayMark.getOffset();
285                     } else { // no more marks
286
arrayMark = null;
287                         arrayMarkOffset = Integer.MAX_VALUE;
288                     }
289                 }
290                 // Insert the listmark
291
insertMark(arrayMarkIndex, listMark);
292                 if (debug) {
293                     /*DEBUG*/System.err.println("Inserted mark at ind=" // NOI18N
294
+ arrayMarkIndex + ": " + listMark); // NOI18N
295
}
296                 arrayMarkIndex++;
297             }
298         }
299     }
300     
301     private void updateFolds(List tokenList, FoldHierarchyTransaction transaction)
302     throws BadLocationException JavaDoc {
303         
304         if (tokenList.size() > 0) {
305             processTokenList(tokenList, transaction);
306         }
307
308         if (maxUpdateMarkOffset == -1) { // no updates
309
return;
310         }
311         
312         // Find the first mark to update and init the prevMark and parentMark prior the loop
313
int index = findMarkIndex(minUpdateMarkOffset);
314         FoldMarkInfo prevMark;
315         FoldMarkInfo parentMark;
316         if (index == 0) { // start from begining
317
prevMark = null;
318             parentMark = null;
319         } else {
320             prevMark = getMark(index - 1);
321             parentMark = prevMark.getParentMark();
322         }
323         
324         // Iterate through the changed marks in the mark array
325
int markCount = getMarkCount();
326         while (index < markCount) { // process the marks
327
FoldMarkInfo mark = getMark(index);
328
329             // If the mark was released then it must be removed
330
if (mark.isReleased()) {
331                 if (debug) {
332                     /*DEBUG*/System.err.println("Removing released mark at ind=" // NOI18N
333
+ index + ": " + mark); // NOI18N
334
}
335                 removeMark(index);
336                 markCount--;
337                 continue;
338             }
339
340             // Update mark's status (folds, parentMark etc.)
341
if (mark.isStartMark()) { // starting a new fold
342
if (prevMark == null || prevMark.isStartMark()) { // new level
343
mark.setParentMark(prevMark); // prevMark == null means root level
344
parentMark = prevMark;
345
346                 } // same level => parent to the parent of the prevMark
347

348             } else { // end mark
349
if (prevMark != null) {
350                     if (prevMark.isStartMark()) { // closing nearest fold
351
prevMark.setEndMark(mark, false, transaction);
352
353                     } else { // prevMark is end mark - closing its parent fold
354
if (parentMark != null) {
355                             // mark's parent gets set as well
356
parentMark.setEndMark(mark, false, transaction);
357                             parentMark = parentMark.getParentMark();
358
359                         } else { // prevMark's parentMark is null (top level)
360
mark.makeSolitaire(false, transaction);
361                         }
362                     }
363                     
364                 } else { // prevMark is null
365
mark.makeSolitaire(false, transaction);
366                 }
367             }
368
369             // Set parent mark of the mark
370
mark.setParentMark(parentMark);
371
372             
373             prevMark = mark;
374             index++;
375         }
376         
377         minUpdateMarkOffset = Integer.MAX_VALUE;
378         maxUpdateMarkOffset = -1;
379         
380         if (debug) {
381             /*DEBUG*/System.err.println("MARKS DUMP:\n" + this);
382         }
383     }
384     
385     public String JavaDoc toString() {
386         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
387         int markCount = getMarkCount();
388         int markCountDigitCount = Integer.toString(markCount).length();
389         for (int i = 0; i < markCount; i++) {
390             sb.append("["); // NOI18N
391
String JavaDoc iStr = Integer.toString(i);
392             appendSpaces(sb, markCountDigitCount - iStr.length());
393             sb.append(iStr);
394             sb.append("]:"); // NOI18N
395
FoldMarkInfo mark = getMark(i);
396             
397             // Add extra indent regarding the depth in hierarchy
398
int indent = 0;
399             FoldMarkInfo parentMark = mark.getParentMark();
400             while (parentMark != null) {
401                 indent += 4;
402                 parentMark = parentMark.getParentMark();
403             }
404             appendSpaces(sb, indent);
405
406             sb.append(mark);
407             sb.append('\n');
408         }
409         return sb.toString();
410     }
411     
412     private static void appendSpaces(StringBuffer JavaDoc sb, int spaces) {
413         while (--spaces >= 0) {
414             sb.append(' ');
415         }
416     }
417
418     private static Pattern JavaDoc pattern = Pattern.compile("(<\\s*editor-fold(?:(?:\\s+id=\"(\\S*)\")?(?:\\s+defaultstate=\"(\\S*)\")?(?:\\s+desc=\"([\\S \\t]*)\")?)(?:(?:\\s+defaultstate=\"(\\S*)\")?(?:\\s+desc=\"([\\S \\t]*)\")?)(?:\\s+defaultstate=\"(\\S*)\")?\\s*>)|(?:</\\s*editor-fold\\s*>)"); // NOI18N
419

420     private FoldMarkInfo scanToken(SyntaxUpdateTokens.TokenInfo tokenInfo) throws BadLocationException JavaDoc {
421         Matcher JavaDoc matcher = pattern.matcher(DocumentUtilities.getText(doc, tokenInfo.getOffset(), tokenInfo.getLength()));
422         if (matcher.find()) {
423             if (matcher.group(1) != null) { // fold's start mark found
424
boolean state = "collapsed".equals(matcher.group(3)); // remember the defaultstate // NOI18N
425
if (matcher.group(2) != null) { // fold's id exists
426
Boolean JavaDoc collapsed = (Boolean JavaDoc)customFoldId.get(matcher.group(2));
427                     if (collapsed != null)
428                         state = collapsed.booleanValue(); // fold's state is already known from the past
429
else
430                         customFoldId.put(matcher.group(2), Boolean.valueOf(state));
431                 }
432                 return new FoldMarkInfo(true, tokenInfo.getOffset(), tokenInfo.getLength(), matcher.group(2), state, matcher.group(4)); // NOI18N
433
} else { // fold's end mark found
434
return new FoldMarkInfo(false, tokenInfo.getOffset(), tokenInfo.getLength(), null, false, null);
435             }
436         }
437         return null;
438     }
439
440     private final class FoldMarkInfo {
441
442         private boolean startMark;
443         private Position JavaDoc pos;
444         private int length;
445         private String JavaDoc id;
446         private boolean collapsed;
447         private String JavaDoc description;
448
449         /** Matching pair mark used for fold construction */
450         private FoldMarkInfo pairMark;
451         
452         /** Parent mark defining nesting in the mark hierarchy. */
453         private FoldMarkInfo parentMark;
454         
455         /**
456          * Fold that corresponds to this mark (if it's start mark).
457          * It can be null if this mark is end mark or if it currently
458          * does not have the fold assigned.
459          */

460         private Fold fold;
461         
462         private boolean released;
463         
464         private FoldMarkInfo(boolean startMark, int offset,
465                              int length, String JavaDoc id, boolean collapsed, String JavaDoc description)
466         throws BadLocationException JavaDoc {
467
468             this.startMark = startMark;
469             this.pos = doc.createPosition(offset);
470             this.length = length;
471             this.id = id;
472             this.collapsed = collapsed;
473             this.description = description;
474         }
475
476         public String JavaDoc getId() {
477             return id;
478         }
479
480         public String JavaDoc getDescription() {
481             return description;
482         }
483
484         public boolean isStartMark() {
485             return startMark;
486         }
487
488         public int getLength() {
489             return length;
490         }
491
492         public int getOffset() {
493             return pos.getOffset();
494         }
495         
496         public int getEndOffset() {
497             return getOffset() + getLength();
498         }
499
500         public boolean isCollapsed() {
501             return (fold != null) ? fold.isCollapsed() : collapsed;
502         }
503         
504         public boolean hasFold() {
505             return (fold != null);
506         }
507         
508         public void setCollapsed(boolean collapsed) {
509             this.collapsed = collapsed;
510         }
511         
512         public boolean isSolitaire() {
513             return (pairMark == null);
514         }
515         
516         public void makeSolitaire(boolean forced, FoldHierarchyTransaction transaction) {
517             if (!isSolitaire()) {
518                 if (isStartMark()) {
519                     setEndMark(null, forced, transaction);
520                 } else { // end mark
521
getPairMark().setEndMark(null, forced, transaction);
522                 }
523             }
524         }
525         
526         public boolean isReleased() {
527             return released;
528         }
529         
530         /**
531          * Release this mark and mark for update.
532          */

533         public void release(boolean forced, FoldHierarchyTransaction transaction) {
534             if (!released) {
535                 makeSolitaire(forced, transaction);
536                 released = true;
537                 markUpdate(this);
538             }
539         }
540         
541         public FoldMarkInfo getPairMark() {
542             return pairMark;
543         }
544         
545         private void setPairMark(FoldMarkInfo pairMark) {
546             this.pairMark = pairMark;
547         }
548
549         public void setEndMark(FoldMarkInfo endMark, boolean forced,
550         FoldHierarchyTransaction transaction) {
551             if (!isStartMark()) {
552                 throw new IllegalStateException JavaDoc("Not start mark"); // NOI18N
553
}
554             if (pairMark == endMark) {
555                 return;
556             }
557             
558             if (pairMark != null) { // is currently paired to an end mark
559
releaseFold(forced, transaction);
560                 pairMark.setPairMark(null);
561             }
562
563             pairMark = endMark;
564             if (endMark != null) {
565                 if (!endMark.isSolitaire()) { // make solitaire first
566
endMark.makeSolitaire(false, transaction); // not forced here
567
}
568                 endMark.setPairMark(this);
569                 endMark.setParentMark(this.getParentMark());
570                 ensureFoldExists(transaction);
571             }
572         }
573         
574         public FoldMarkInfo getParentMark() {
575             return parentMark;
576         }
577         
578         public void setParentMark(FoldMarkInfo parentMark) {
579             this.parentMark = parentMark;
580         }
581         
582         private void releaseFold(boolean forced, FoldHierarchyTransaction transaction) {
583             if (isSolitaire() || !isStartMark()) {
584                throw new IllegalStateException JavaDoc();
585             }
586
587             if (fold != null) {
588                 setCollapsed(fold.isCollapsed()); // serialize the collapsed info
589
if (!forced) {
590                     getOperation().removeFromHierarchy(fold, transaction);
591                 }
592                 fold = null;
593             }
594         }
595
596         public Fold getFold() {
597             if (isSolitaire()) {
598                 return null;
599             }
600             if (!isStartMark()) {
601                 return pairMark.getFold();
602             }
603             return fold;
604         }
605         
606         public void ensureFoldExists(FoldHierarchyTransaction transaction) {
607             if (isSolitaire() || !isStartMark()) {
608                 throw new IllegalStateException JavaDoc();
609             }
610
611             if (fold == null) {
612                 try {
613                     if (!startMark) {
614                         throw new IllegalStateException JavaDoc("Not start mark: " + this); // NOI18N
615
}
616                     if (pairMark == null) {
617                         throw new IllegalStateException JavaDoc("No pairMark for mark:" + this); // NOI18N
618
}
619                     int startOffset = getOffset();
620                     int startGuardedLength = getLength();
621                     int endGuardedLength = pairMark.getLength();
622                     int endOffset = pairMark.getOffset() + endGuardedLength;
623                     fold = getOperation().addToHierarchy(
624                         CUSTOM_FOLD_TYPE, getDescription(), collapsed,
625                         startOffset, endOffset,
626                         startGuardedLength, endGuardedLength,
627                         this,
628                         transaction
629                     );
630                 } catch (BadLocationException JavaDoc e) {
631                     ErrorManager.getDefault().notify(e);
632                 }
633             }
634         }
635         
636         public String JavaDoc toString() {
637             StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
638             sb.append(isStartMark() ? 'S' : 'E'); // NOI18N
639

640             // Check whether this mark (or its pair) has fold
641
if (hasFold() || (!isSolitaire() && getPairMark().hasFold())) {
642                 sb.append("F"); // NOI18N
643

644                 // Check fold's status
645
if (isStartMark() && (isSolitaire()
646                         || getOffset() != fold.getStartOffset()
647                         || getPairMark().getEndOffset() != fold.getEndOffset())
648                 ) {
649                     sb.append("!!<"); // NOI18N
650
sb.append(fold.getStartOffset());
651                     sb.append(","); // NOI18N
652
sb.append(fold.getEndOffset());
653                     sb.append(">!!"); // NOI18N
654
}
655             }
656
657             // Append mark's internal status
658
sb.append(" ("); // NOI18N
659
sb.append("o="); // NOI18N
660
sb.append(pos.getOffset());
661             sb.append(", l="); // NOI18N
662
sb.append(length);
663             sb.append(", d='"); // NOI18N
664
sb.append(description);
665             sb.append('\'');
666             if (getPairMark() != null) {
667                 sb.append(", <->"); // NOI18N
668
sb.append(getPairMark().getOffset());
669             }
670             if (getParentMark() != null) {
671                 sb.append(", ^"); // NOI18N
672
sb.append(getParentMark().getOffset());
673             }
674             sb.append(')');
675             
676             return sb.toString();
677         }
678
679     }
680         
681     public static final class Factory implements FoldManagerFactory {
682
683         public FoldManager createFoldManager() {
684             return new CustomFoldManager();
685         }
686     }
687 }
688
Popular Tags