KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > html > editor > folding > HTMLFoldManager


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.modules.html.editor.folding;
21
22 import java.lang.reflect.InvocationTargetException JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Hashtable JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.List JavaDoc;
28 import java.util.Stack JavaDoc;
29 import java.util.Timer JavaDoc;
30 import java.util.TimerTask JavaDoc;
31 import javax.swing.SwingUtilities JavaDoc;
32 import javax.swing.event.DocumentEvent JavaDoc;
33 import javax.swing.text.BadLocationException JavaDoc;
34 import javax.swing.text.Document JavaDoc;
35 import javax.swing.text.JTextComponent JavaDoc;
36
37 import org.netbeans.editor.BaseDocument;
38 import org.netbeans.editor.Settings;
39 import org.netbeans.editor.SettingsChangeEvent;
40 import org.netbeans.editor.SettingsChangeListener;
41 import org.netbeans.editor.SettingsUtil;
42 import org.netbeans.editor.Utilities;
43 import org.netbeans.editor.ext.html.HTMLSettingsDefaults;
44 import org.netbeans.editor.ext.html.HTMLSettingsNames;
45 import org.netbeans.editor.ext.html.HTMLSyntaxSupport;
46 import org.netbeans.editor.ext.html.parser.SyntaxElement;
47
48 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
49 import org.netbeans.spi.editor.fold.FoldManager;
50 import org.netbeans.spi.editor.fold.FoldOperation;
51 import org.netbeans.api.editor.fold.Fold;
52 import org.netbeans.api.editor.fold.FoldHierarchy;
53 import org.netbeans.api.editor.fold.FoldType;
54 import org.netbeans.api.editor.fold.FoldUtilities;
55 import org.openide.ErrorManager;
56
57 /**
58  * This class is an implementation of @see org.netbeans.spi.editor.fold.FoldManager
59  * responsible for creating, deleting and updating code folds.
60  *
61  * @author Marek Fukala
62  */

63
64 public class HTMLFoldManager implements FoldManager, SettingsChangeListener {
65     
66     private static final boolean SHOW_TIMES = Boolean.getBoolean("org.netbeans.modules.html.editor.folding.measure");
67     
68     private FoldOperation operation;
69     private HTMLSyntaxSupport sup;
70     
71     //timer performing periodicall folds update
72
private Timer JavaDoc timer;
73     private TimerTask JavaDoc timerTask;
74     private int foldsUpdateInterval = -1;
75     
76     boolean documentDirty = true;
77     private static final String JavaDoc FOLD_MANAGER_CREATED = "FOLD_MANAGER_CREATED"; //NOI18N
78

79     private BaseDocument doc = null;
80     
81     protected FoldOperation getOperation() {
82         return operation;
83     }
84     
85     public void init(FoldOperation operation) {
86         this.operation = operation;
87         Settings.addSettingsChangeListener(this);
88         foldsUpdateInterval = getSetting(HTMLSettingsNames.CODE_FOLDING_UPDATE_TIMEOUT);
89     }
90     
91     public void initFolds(FoldHierarchyTransaction transaction) {
92         //filter first initFolds call when the EditorPane has PlainDocument content
93
Document JavaDoc doc = getOperation().getHierarchy().getComponent().getDocument();
94         
95         if(doc instanceof BaseDocument
96                 // a workaround for multiple calls from the infrastructure (issue 84598)
97
&& doc.getLength() > 0 && doc.getProperty(FOLD_MANAGER_CREATED) == null) {
98             
99             this.doc = (BaseDocument)doc;
100             doc.putProperty(FOLD_MANAGER_CREATED, new Object JavaDoc());
101             
102             sup = new HTMLSyntaxSupport(getDocument());
103             
104             //start folds updater timer
105
//put off the initial fold search due to the processor overhead during page opening
106
timer = new Timer JavaDoc();
107             restartTimer();
108         }
109     }
110     
111     private BaseDocument getDocument() {
112         return this.doc;
113     }
114     
115     private void restartTimer() {
116         documentDirty = true;
117         //test whether the FoldManager.release() was called.
118
//if so, then do not try to update folds anymore
119
if(timer == null) return ;
120         
121         if(timerTask != null) timerTask.cancel();
122         timerTask = createTimerTask();
123         timer.schedule(timerTask, foldsUpdateInterval);
124     }
125     
126     private TimerTask JavaDoc createTimerTask() {
127         return new TimerTask JavaDoc() {
128             public void run() {
129                 //set the update thread priority
130
Thread JavaDoc thr = new Thread JavaDoc(new Runnable JavaDoc() {
131                     public void run() {
132                         try {
133                             documentDirty = false;
134                             updateFolds();
135                         }catch(ParsingCancelledException pce) {
136                             if(debug) System.out.println("parsing cancelled");
137                         }
138                     }
139                 });
140                 thr.setPriority(Thread.MIN_PRIORITY + 1);
141                 thr.start();
142                 //wait for the thread to die
143
try {
144                     thr.join();
145                 }catch(InterruptedException JavaDoc e) {
146                     //ignore
147
}
148             }
149         };
150     }
151     
152     public void release() {
153         Settings.removeSettingsChangeListener(this);
154         if (timer != null) {
155             timer.cancel();
156             timer = null;
157         }
158     }
159     
160     public void insertUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
161         restartTimer();
162     }
163     
164     public void removeUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
165         restartTimer();
166     }
167     
168     public void changedUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
169         //do nothing - the updates are catched in insertUpdate and removeUpdate methods
170
}
171     
172     public void removeEmptyNotify(Fold epmtyFold) {
173     }
174     
175     public void removeDamagedNotify(Fold damagedFold) {
176     }
177     
178     public void expandNotify(Fold expandedFold) {
179     }
180     
181     public void settingsChange(SettingsChangeEvent evt) {
182         // Get folding presets
183
if(evt.getSettingName() == HTMLSettingsNames.CODE_FOLDING_UPDATE_TIMEOUT) {
184             foldsUpdateInterval = getSetting(HTMLSettingsNames.CODE_FOLDING_UPDATE_TIMEOUT);
185             restartTimer();
186         }
187     }
188     
189     private List JavaDoc generateFolds() throws BadLocationException JavaDoc, ParsingCancelledException {
190         return generateFolds((HTMLSyntaxSupport)getDocument().getSyntaxSupport());
191     }
192     
193     List JavaDoc generateFolds(HTMLSyntaxSupport sup) throws BadLocationException JavaDoc, ParsingCancelledException {
194         Stack JavaDoc stack = new Stack JavaDoc(); //we need this to determine tags nesting
195
ArrayList JavaDoc generated = new ArrayList JavaDoc(100);
196         
197         SyntaxElement sel = sup.getElementChain(0);
198         while(sel != null) {
199             //check if the parsing should be cancelled (when there is a change in the parsed document)
200
if(documentDirty) throw new ParsingCancelledException();
201             
202 // System.out.println(sel + ">>>" + doc.getText(sel.getElementOffset(), sel.getElementLength()) + "<<<");
203

204             if(sel.getType() == SyntaxElement.TYPE_TAG) {
205                 //found open tag
206
SyntaxElement.Tag tag = (SyntaxElement.Tag)sel;
207                 if(tag.isEmpty()) {
208                     //create element - do not put into stack
209
generated.add(new FoldInfo(sel.getElementOffset(), sel.getElementOffset() + sel.getElementLength(), HTMLFoldTypes.TAG, getSingletonTagFoldName(tag.getName())));
210                 } else {
211                     stack.push(sel);
212                 }
213             } else if(sel.getType() == SyntaxElement.TYPE_ENDTAG) {
214                 //found end tag
215
SyntaxElement.Named endtag = (SyntaxElement.Named)sel;
216                 if(!stack.isEmpty()) {
217                     SyntaxElement top = (SyntaxElement)stack.peek();
218                     if(top.getType() == SyntaxElement.TYPE_TAG
219                             && ((SyntaxElement.Tag)top).getName().equalsIgnoreCase(endtag.getName())) {
220                         //we found corresponding open tag
221
generated.add(new FoldInfo(top.getElementOffset(), endtag.getElementOffset() + endtag.getElementLength(), HTMLFoldTypes.TAG, getTagFoldName(((SyntaxElement.Tag)top).getName())));
222                         stack.pop();
223                     } else {
224                         //I need to save the pop-ed elements for the case that there isn't
225
//any matching start tag found
226
ArrayList JavaDoc savedElements = new ArrayList JavaDoc();
227                         //this semaphore is used behind the loop to detect whether a
228
//matching start has been found
229
boolean foundStartTag = false;
230                         
231                         while(!stack.isEmpty()) {
232                             SyntaxElement.Tag start = (SyntaxElement.Tag)stack.pop();
233                             savedElements.add(start);
234                             
235                             if(start.getName().equalsIgnoreCase(endtag.getName())) {
236                                 //found a matching start tag
237
generated.add(new FoldInfo(start.getElementOffset(),
238                                         endtag.getElementOffset() + endtag.getElementLength(),
239                                         HTMLFoldTypes.TAG, getTagFoldName(start.getName())));
240                                 
241                                 foundStartTag = true;
242                                 break; //break the while loop
243
}
244                         }
245                         if(!foundStartTag) {
246                             //we didn't find any matching start tag =>
247
//return all elements back to the stack
248
for(int i = savedElements.size() - 1; i >= 0; i--) {
249                                 stack.push(savedElements.get(i));
250                             }
251                         }
252                     }
253                 }
254             } else if(sel.getType() == SyntaxElement.TYPE_COMMENT) {
255                 //found comment
256
generated.add(new FoldInfo(sel.getElementOffset(), sel.getElementOffset() + sel.getElementLength() - 1, HTMLFoldTypes.COMMENT, HTMLFoldTypes.COMMENT_DESCRIPTION));
257             }
258             //do not bother with the rest of elements so far
259

260             //get next syntax element
261
sel = getNextSyntaxElement(sel);
262         }
263         return generated;
264     }
265     
266     private String JavaDoc getSingletonTagFoldName(String JavaDoc tagName) {
267         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
268         sb.append('<');
269         sb.append(tagName);
270         sb.append('/');
271         sb.append('>');
272         return sb.toString();
273     }
274     
275     private String JavaDoc getTagFoldName(String JavaDoc tagName) {
276         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
277         sb.append('<');
278         sb.append(tagName);
279         sb.append(">...</");
280         sb.append(tagName);
281         sb.append('>');
282         return sb.toString();
283     }
284     
285     /** The heart of this class. This method parses the JSP page and based on
286      * syntax parser information creates appropriate folds.
287      */

288     private synchronized void updateFolds() throws ParsingCancelledException {
289         FoldHierarchy fh = getOperation().getHierarchy();
290         
291         //measure folds generation time
292
long startTime = System.currentTimeMillis();
293         
294         try {
295             //parse document and create a list of FoldInfo-s
296
List JavaDoc generated = generateFolds();
297             
298             if(SHOW_TIMES) System.out.println("[html folding] parsing of text of " + getDocument().getProperty(Document.TitleProperty) + " done in " + (System.currentTimeMillis() - startTime) + " millis.");
299             
300 // //the timer is set to null when release() is called on this FoldManager => document is about to be closed
301
// if(timer == null) {
302
// if (debug) System.out.println("release() called -> cancelling folds update"); // NOI18N
303
// return ;
304
// }
305
//filter out one-line folds
306
Iterator JavaDoc itr = generated.iterator();
307             HashSet JavaDoc olfs = new HashSet JavaDoc();
308             while (itr.hasNext()) {
309                 FoldInfo elem = (FoldInfo) itr.next();
310                 if(isOneLineElement(elem)) olfs.add(elem);
311             }
312             generated.removeAll(olfs);
313             
314             //get existing folds
315
List JavaDoc existingFolds = FoldUtilities.findRecursive(fh.getRootFold());
316             assert existingFolds != null : "Existing folds is null!"; // NOI18N
317

318             //...and generate a list of new folds and a list of folds to be removed
319
final ArrayList JavaDoc/*<FoldInfo>*/ newborns = new ArrayList JavaDoc(generated.size() / 2);
320             final ArrayList JavaDoc/*<Fold>*/ zombies = new ArrayList JavaDoc(generated.size() / 2);
321             
322             //go through all the parsed elements and compare it with the list of existing folds
323
Iterator JavaDoc genItr = generated.iterator();
324             Hashtable JavaDoc newbornsLinesCache = new Hashtable JavaDoc();
325             ArrayList JavaDoc duplicateNewborns = new ArrayList JavaDoc();
326             while(genItr.hasNext()) {
327                 FoldInfo fi = (FoldInfo)genItr.next();
328                 if(debug) System.out.println("NEWBORN " + fi);
329                 //do not add more newborns with the same lineoffset
330
int fiLineOffset = Utilities.getLineOffset(getDocument(), fi.startOffset);
331                 FoldInfo found = (FoldInfo)newbornsLinesCache.get(Integer.valueOf(fiLineOffset));
332                 if(found != null) {
333                     //figure out whether the new element is a descendant of the already added one
334
if(found.endOffset < fi.endOffset) {
335                         //remove the descendant and add the current
336
duplicateNewborns.add(found);
337                     }
338                 }
339                 newbornsLinesCache.put(Integer.valueOf(fiLineOffset), fi); //add line mapping of the current element
340

341                 //try to find a fold for the fold info
342
Fold fs = FoldUtilities.findNearestFold(fh, fi.startOffset);
343                 if(fs != null
344                         && fs.getStartOffset() == fi.startOffset
345                         && fs.getEndOffset() == fi.endOffset) {
346                     //there is a fold with the same boundaries as the FoldInfo
347
if(fi.foldType != fs.getType() || !(fi.description.equals(fs.getDescription()))) {
348                         //the fold has different type or/and description => recreate
349
zombies.add(fs);
350                         newborns.add(fi);
351                     }
352                 } else {
353                     //create a new fold
354
newborns.add(fi);
355                 }
356             }
357             newborns.removeAll(duplicateNewborns);
358             existingFolds.removeAll(zombies);
359             
360             Hashtable JavaDoc linesToFoldsCache = new Hashtable JavaDoc(); //needed by ***
361

362             //remove not existing folds
363
Iterator JavaDoc extItr = existingFolds.iterator();
364             while(extItr.hasNext()) {
365                 Fold f = (Fold)extItr.next();
366 // if(!zombies.contains(f)) { //check if not alread scheduled to remove
367
Iterator JavaDoc genItr2 = generated.iterator();
368                 boolean found = false;
369                 while(genItr2.hasNext()) {
370                     FoldInfo fi = (FoldInfo)genItr2.next();
371                     if(f.getStartOffset() == fi.startOffset
372                             && f.getEndOffset() == fi.endOffset) {
373                         found = true;
374                         break;
375                     }
376                 }
377                 if(!found) {
378                     zombies.add(f);
379                 } else {
380                     //store the fold lineoffset 2 fold mapping
381
int lineoffset = Utilities.getLineOffset(getDocument(), f.getStartOffset());
382                     linesToFoldsCache.put(Integer.valueOf(lineoffset), f);
383                 }
384 // }
385
}
386             
387             //*** check for all newborns if there isn't any existing fold
388
//starting on the same line which is a descendant of this new fold
389
//if so remove it.
390
Iterator JavaDoc newbornsItr = newborns.iterator();
391             ArrayList JavaDoc newbornsToRemove = new ArrayList JavaDoc();
392             while(newbornsItr.hasNext()) {
393                 FoldInfo fi = (FoldInfo)newbornsItr.next();
394                 Fold existing = (Fold)linesToFoldsCache.get(Integer.valueOf(Utilities.getLineOffset(getDocument(), fi.startOffset)));
395                 if(existing != null) {
396                     //test if the fold is my descendant
397
if(existing.getEndOffset() < fi.endOffset) {
398                         //descendant - remove it9
399
zombies.add(existing);
400                     } else {
401                         //remove the newborn
402
newbornsToRemove.add(fi);
403                     }
404                 }
405             }
406             newborns.removeAll(newbornsToRemove);
407             
408             if(SHOW_TIMES) System.out.println("[html folding] parsing and mangles with elements for " + getDocument().getProperty(Document.TitleProperty) + " done in " + (System.currentTimeMillis() - startTime) + " millis.");
409             
410             //run folds update in event dispatching thread
411
SwingUtilities.invokeAndWait(new Runnable JavaDoc() {
412                 public void run() {
413                     if(debug) System.out.println("updating folds --> locking document!"); // NOI18N
414
//lock the document for changes
415
getDocument().readLock();
416                     try {
417                         //lock the hierarchy
418
FoldHierarchy fh = getOperation().getHierarchy();
419                         fh.lock();
420                         try {
421                             //open new transaction
422
FoldHierarchyTransaction fhTran = getOperation().openTransaction();
423                             try {
424                                 //remove outdated folds
425
Iterator JavaDoc i = zombies.iterator();
426                                 while(i.hasNext()) {
427                                     Fold f = (Fold)i.next();
428                                     //test whether the size of the document is greater than zero,
429
//if it is then this means that the document has been closed in editor.
430
if(getDocument().getLength() == 0) break;
431                                     
432                                     if(debug) System.out.println("- removing fold " + f);
433                                     getOperation().removeFromHierarchy(f, fhTran);
434                                 }
435                                 
436                                 //add new folds
437
Iterator JavaDoc newFolds = newborns.iterator();
438                                 while(newFolds.hasNext()) {
439                                     FoldInfo f = (FoldInfo)newFolds.next();
440                                     //test whether the size of the document is greater than zero,
441
//if it is then this means that the document has been closed in editor.
442
if(getDocument().getLength() == 0) break;
443                                     
444                                     if(f.startOffset >= 0
445                                             && f.endOffset >= 0
446                                             && f.startOffset < f.endOffset
447                                             && f.endOffset <= getDocument().getLength()) {
448                                         getOperation().addToHierarchy(f.foldType, f.description, false, f.startOffset , f.endOffset , 0, 0, null, fhTran);
449                                     }
450                                 }
451                             }catch(BadLocationException JavaDoc ble) {
452                                 //when the document is closing the hierarchy returns different empty document, grrrr
453
Document JavaDoc fhDoc = getOperation().getHierarchy().getComponent().getDocument();
454                                 if(fhDoc.getLength() > 0) ErrorManager.getDefault().notify(ble);
455                             }finally {
456                                 fhTran.commit();
457                             }
458                         } finally {
459                             fh.unlock();
460                         }
461                     } finally {
462                         getDocument().readUnlock();
463                     }
464                     if(debug) System.out.println("document unlocked!"); // NOI18N
465
}
466             });
467             
468             
469         }catch(BadLocationException JavaDoc e) {
470             //in case that the document is about to be closed
471
//the BLE can be throws from some editor utility classes
472
//so we can swallow it in this case
473

474             //when the document is closing the hierarchy returns different empty document, grrrr
475
Document JavaDoc fhDoc = getOperation().getHierarchy().getComponent().getDocument();
476             if(fhDoc.getLength() > 0) ErrorManager.getDefault().notify(e);
477         }catch(InterruptedException JavaDoc ie) {
478             //do nothing
479
}catch(InvocationTargetException JavaDoc ite) {
480             ErrorManager.getDefault().notify(ite);
481         }catch(ParsingCancelledException pce) {
482             throw new ParsingCancelledException();
483         }catch(Exception JavaDoc e) {
484             //do not let exceptions like NPEs to fall through to the timer's task run method.
485
//if this happens the timer is cancelled and cannot be used anymore
486
ErrorManager.getDefault().notify(e);
487         } finally {
488             if(debug) HTMLFoldUtils.printFolds(getOperation()); //DEBUG - print folds structure into console
489
}
490         
491         //measure folds generation time
492
long foldsGenerationTime = System.currentTimeMillis() - startTime;
493         if(SHOW_TIMES) System.out.println("[html folding] folds for " + getDocument().getProperty(Document.TitleProperty) + " generated in " + foldsGenerationTime + " millis.");
494         
495     }
496     
497     private boolean isOneLineElement(FoldInfo fi) throws BadLocationException JavaDoc {
498         return Utilities.getLineOffset(getDocument(), fi.startOffset) == Utilities.getLineOffset(getDocument(), fi.endOffset);
499     }
500     
501     private boolean foldsBoundariesEquals(Fold f1, Fold f2) {
502         return (f1.getStartOffset() == f2.getStartOffset()
503         && f1.getEndOffset() == f2.getEndOffset());
504     }
505     
506     //XXX SyntaxElement hack since it is quite dangerous to patch the SEs itselft
507
private SyntaxElement getNextSyntaxElement(SyntaxElement se) throws BadLocationException JavaDoc {
508         return se.getNext();
509 // SyntaxElement next = se.getNext();
510
// if(next != null && next.getType() == SyntaxElement.TYPE_TAG && ((SyntaxElement.Named)next).getName().startsWith("<")) {
511
// next = next.getNext();
512
// if(next == null) return null;
513
// }
514
// return next;
515
}
516     
517     private int getSetting(String JavaDoc settingName){
518         JTextComponent JavaDoc tc = getOperation().getHierarchy().getComponent();
519         return SettingsUtil.getInteger(org.netbeans.editor.Utilities.getKitClass(tc), settingName, HTMLSettingsDefaults.defaultCodeFoldingUpdateInterval);
520     }
521     
522     private static final boolean debug = false; //enable/disable debugging messages for this class
523

524     static class FoldInfo {
525         public int startOffset, endOffset;
526         public FoldType foldType = null;
527         public String JavaDoc description = null;
528         
529         public FoldInfo(int startOffset, int endOffset, FoldType foldType, String JavaDoc description) {
530             this.startOffset = startOffset;
531             this.endOffset = endOffset;
532             this.foldType = foldType;
533             this.description = description;
534         }
535         
536         public String JavaDoc toString() {
537             return "FI(" + description + ", " + foldType + ", <" + startOffset + "-" + endOffset + ">)";
538         }
539     }
540     
541     static class ParsingCancelledException extends Exception JavaDoc {
542         public ParsingCancelledException() {
543             super();
544         }
545     }
546 }
547
Popular Tags