KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > xml > text > folding > XmlFoldManager


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.xml.text.folding;
21 import java.util.Iterator JavaDoc;
22 import java.util.Timer JavaDoc;
23 import java.util.TimerTask JavaDoc;
24 import java.util.Vector JavaDoc;
25 import javax.swing.event.DocumentEvent JavaDoc;
26 import javax.swing.text.AbstractDocument JavaDoc;
27 import javax.swing.text.BadLocationException JavaDoc;
28 import javax.swing.text.Document JavaDoc;
29 import org.netbeans.api.editor.fold.Fold;
30 import org.netbeans.api.editor.fold.FoldHierarchy;
31 import org.netbeans.api.editor.fold.FoldType;
32 import org.netbeans.api.editor.fold.FoldUtilities;
33 import org.netbeans.editor.BaseDocument;
34 import org.netbeans.editor.Settings;
35 import org.netbeans.editor.SettingsChangeEvent;
36 import org.netbeans.editor.SettingsChangeListener;
37 import org.netbeans.editor.Utilities;
38 import org.netbeans.modules.editor.structure.api.DocumentElement;
39 import org.netbeans.modules.editor.structure.api.DocumentModel;
40 import org.netbeans.modules.editor.structure.api.DocumentModelException;
41 import org.netbeans.modules.editor.structure.api.DocumentModelListener;
42 import org.netbeans.modules.xml.text.structure.XMLDocumentModelProvider;
43 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
44 import org.netbeans.spi.editor.fold.FoldManager;
45 import org.netbeans.spi.editor.fold.FoldOperation;
46 import org.openide.ErrorManager;
47 import org.openide.util.NbBundle;
48
49
50 /**
51  * This class is an implementation of @see org.netbeans.spi.editor.fold.FoldManager
52  * responsible for creating, deleting and updating code folds.
53  *
54  * @author Marek Fukala
55  */

56 public class XmlFoldManager implements FoldManager, SettingsChangeListener, DocumentModelListener {
57     
58     private FoldOperation operation;
59     
60     //timer performing periodicall folds update
61
private Timer JavaDoc timer;
62     private TimerTask JavaDoc timerTask;
63     
64     private int foldsUpdateInterval = 500;
65     private long foldsGenerationTime = -1;
66     
67     private DocumentModel model = null;
68     
69     //stores changes in document model between fold updates
70
private Vector JavaDoc changes = new Vector JavaDoc();
71     
72     protected FoldOperation getOperation() {
73         return operation;
74     }
75     
76     public void init(FoldOperation operation) {
77         this.operation = operation;
78         Settings.addSettingsChangeListener(this);
79 // foldsUpdateInterval = getSetting(JspSettings.CODE_FOLDING_UPDATE_TIMEOUT);
80
}
81     
82     //fold hiearchy has been released
83
public void release() {
84         Settings.removeSettingsChangeListener(this);
85         
86         if(timer != null) {
87             timer.cancel();
88             timer = null;
89         }
90         
91         if(model != null) {
92             model.removeDocumentModelListener(this);
93             model = null;
94         }
95     }
96     
97     public void initFolds(FoldHierarchyTransaction transaction) {
98         //I do not know exactly why, but this method is called twice during the initialization
99
//during first call getDocument() doesn't return an instance of BaseDocument
100
if(!(getDocument() instanceof BaseDocument)) return ;
101         
102         //the initFolds is called when the document is disposed - I need to filter this call
103
if(getDocument().getLength() > 0) {
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     
112     //init the folds - it must be done since the folds
113
//are created based on events fired from model and the model
114
private void initModelAndFolds() {
115         try {
116             model = DocumentModel.getDocumentModel((BaseDocument)getDocument());
117             //add changes listener which listenes to model changes
118
model.addDocumentModelListener(this);
119             //add all existing elements to the changes list
120
//the changes will be subsequently transformed to folds
121
addElementsRecursivelly(changes, model.getRootElement());
122         } catch (DocumentModelException e) {
123             ErrorManager.getDefault().notify(e);
124         }
125     }
126     
127     private void addElementsRecursivelly(Vector JavaDoc changes, DocumentElement de) {
128         //add myself
129
try {
130             if(!de.equals(model.getRootElement()) && !isOneLineElement(de)) changes.add(new DocumentModelChangeInfo(de, DocumentModelChangeInfo.ELEMENT_ADDED));
131         }catch(BadLocationException JavaDoc e) {
132             ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
133         }
134         //add my children
135
Iterator JavaDoc children = de.getChildren().iterator();
136         while(children.hasNext()) {
137             DocumentElement child = (DocumentElement)children.next();
138             addElementsRecursivelly(changes, child);
139         }
140     }
141     
142     public void documentElementAdded(DocumentElement de) {
143         if(debug) System.out.println("[xmlfolding] ADDED " + de);
144         checkElement2FoldConsistency(de, false);
145     }
146     
147     public void documentElementRemoved(DocumentElement de) {
148         if(debug) System.out.println("[xmlfolding] REMOVED " + de);
149         if(!de.equals(model.getRootElement()) && !de.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) changes.add(new DocumentModelChangeInfo(de, DocumentModelChangeInfo.ELEMENT_REMOVED));
150         checkElement2FoldConsistency(de, true);
151         restartTimer();
152     }
153     
154     public void documentElementChanged(DocumentElement de) {
155         if(debug) System.out.println("[xmlfolding] CONTENT UPDATE " + de);
156         checkElement2FoldConsistency(de, true);
157     }
158     
159     public void checkElement2FoldConsistency(DocumentElement de, boolean removed) {
160         //get leaf element for the changed position (got from the changed element)
161
//this is has to be done since I need to recursivelly check all element's
162
//ancestor, which cannot be done if the element was removed (in such situation
163
//I cannot get parent).
164
DocumentElement tested = removed ? model.getLeafElementForOffset(de.getStartOffset()) : de;
165         boolean restartTimer = false;
166         while(tested != null) {
167             //do not check root element
168
if(tested.equals(model.getRootElement())) break ;
169             
170             if(!tested.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) {
171                 
172                 //check consistency of this
173
try {
174                     Fold existingFold = getFold(getOperation().getHierarchy(), tested);
175                     boolean oneLineElement = isOneLineElement(tested);
176                     if(existingFold != null && oneLineElement) {
177                         //there is already a fold for the element,
178
//but the element was changed so now its end and start offsets
179
//are on the same line => remove the fold
180
changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_REMOVED));
181                         restartTimer = true;
182                     }
183                     if(existingFold == null && !oneLineElement) {
184                         //there wasn't any fold for the element because its start == end,
185
//now the situation changed => add a fold
186
changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_ADDED));
187                         restartTimer = true;
188                     }
189                     if(existingFold != null && !oneLineElement) {
190                         //there is already a fold, test if the fold corresponds to the element
191
//in the case of xml tag check also the element&fold name
192
if(getFoldTypeForElement(tested) != existingFold.getType()
193                         || !existingFold.getDescription().equals("<"+tested.getName()+">")) {
194                             //recreate the fold -- looks silly but works - there is no check for type and name in the updateFolds() method
195
changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_REMOVED));
196                             changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_ADDED));
197                         }
198                     }
199                     
200                 }catch(BadLocationException JavaDoc e) {
201                     ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
202                 }
203             }
204             
205             tested = tested.getParentElement(); //switch to parent
206
}
207         
208         if(restartTimer) restartTimer(); //restart if necessary
209
}
210     
211     private FoldType getFoldTypeForElement(DocumentElement de) {
212         //create folds of appropriate type
213
if(de.getType().equals(XMLDocumentModelProvider.XML_TAG)
214         || de.getType().equals(XMLDocumentModelProvider.XML_TAG)) {
215             return XmlFoldTypes.TAG;
216         } else if(de.getType().equals(XMLDocumentModelProvider.XML_PI)) {
217             return XmlFoldTypes.PI;
218         } else if(de.getType().equals(XMLDocumentModelProvider.XML_DOCTYPE)) {
219             return XmlFoldTypes.DOCTYPE;
220         } else if(de.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) {
221             return XmlFoldTypes.COMMENT;
222         } else if(de.getType().equals(XMLDocumentModelProvider.XML_CDATA)) {
223             return XmlFoldTypes.CDATA;
224         }
225         return null;
226     }
227     
228     public void documentElementAttributesChanged(DocumentElement de) {
229         //do not handle
230
}
231     
232     private void restartTimer() {
233         //test whether the FoldManager.release() was called.
234
//if so, then do not try to update folds anymore
235
if(timer == null) return ;
236         
237         if(timerTask != null) timerTask.cancel();
238         timerTask = createTimerTask();
239         timer.schedule(timerTask, foldsUpdateInterval);
240     }
241     
242     private TimerTask JavaDoc createTimerTask() {
243         return new TimerTask JavaDoc() {
244             public void run() {
245                 try {
246                     if(model == null) initModelAndFolds();
247                     updateFolds();
248                 }catch(Exception JavaDoc e) {
249                     //catch all exceptions to prevent the timer to be cancelled
250
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
251                 }
252             }
253         };
254     }
255     
256     /** Applies changes in the document model to the fold hierarchy
257      */

258     
259     private void updateFolds() {
260         Document JavaDoc doc = getDocument();
261         if(!(doc instanceof AbstractDocument JavaDoc)) return ;
262         
263         ((AbstractDocument JavaDoc)doc).readLock();
264         try {
265             FoldHierarchy fh = getOperation().getHierarchy();
266             fh.lock();
267             try {
268                 FoldHierarchyTransaction fhTran = getOperation().openTransaction();
269                 try {
270                     Iterator JavaDoc changesItr = ((Vector JavaDoc)changes.clone()).iterator(); //clone the vector to prevent concurrent modifications
271
while(changesItr.hasNext()) {
272                         DocumentModelChangeInfo chi = (DocumentModelChangeInfo)changesItr.next();
273                         if(debug) System.out.println("[xmlfolding] processing change " + chi);
274                         DocumentElement de = chi.getDocumentElement();
275                         if(chi.getChangeType() == DocumentModelChangeInfo.ELEMENT_ADDED
276                                 && de.getStartOffset() < de.getEndOffset()
277                                 && !de.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) {
278                             String JavaDoc foldName = "";
279                             FoldType type = XmlFoldTypes.TEXT; //fold of this type should not be ever used
280

281                             //create folds of appropriate type
282
if(de.getType().equals(XMLDocumentModelProvider.XML_TAG)
283                             || de.getType().equals(XMLDocumentModelProvider.XML_EMPTY_TAG)) {
284                                 foldName = "<"+de.getName()+">";
285                                 type = XmlFoldTypes.TAG;
286                             } else if(de.getType().equals(XMLDocumentModelProvider.XML_PI)) {
287                                 foldName = NbBundle.getMessage(XmlFoldManager.class, "LBL_PI"); //NOI18N
288
type = XmlFoldTypes.PI;
289                             } else if(de.getType().equals(XMLDocumentModelProvider.XML_DOCTYPE)) {
290                                 foldName = NbBundle.getMessage(XmlFoldManager.class, "LBL_DOCTYPE"); //NOI18N
291
type = XmlFoldTypes.DOCTYPE;
292                             } else if(de.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) {
293                                 foldName = NbBundle.getMessage(XmlFoldManager.class, "LBL_COMMENT"); //NOI18N
294
type = XmlFoldTypes.COMMENT;
295                             } else if(de.getType().equals(XMLDocumentModelProvider.XML_CDATA)) {
296                                 foldName = NbBundle.getMessage(XmlFoldManager.class, "LBL_CDATA"); //NOI18N
297
type = XmlFoldTypes.CDATA;
298                             }
299                             if(getFold(fh, de) == null) {
300                                 //add the fold only if really doesn't exist yet
301
if(debug) System.out.println("[XML folding] adding fold for " + de);
302                                 getOperation().addToHierarchy(type, foldName, false,
303                                         Math.max(0, de.getStartOffset()) ,
304                                         Math.min(getDocument().getLength(), de.getEndOffset() + 1),
305                                         0, 0, null, fhTran);
306                             }
307                         } else if (chi.getChangeType() == DocumentModelChangeInfo.ELEMENT_REMOVED) {
308                             if(debug) System.out.println("[XML folding] about to remove fold for " + chi.getDocumentElement());
309                             //find appropriate fold for the document element
310
Fold existingFold = getFold(fh, de);
311                             if(existingFold != null) {
312                                 if(debug) System.out.println("[XML folding] removing fold " + chi.getDocumentElement());
313                                 getOperation().removeFromHierarchy(existingFold, fhTran);
314                             }
315                         }
316                     }
317                     
318                 }catch(BadLocationException JavaDoc ble) {
319                     ErrorManager.getDefault().notify(ble);
320                 }finally {
321                     fhTran.commit();
322                 }
323                 
324                 if(debug) {
325                     System.out.println("*************\n\n folds:\n\n");
326                     dumpFolds(fh.getRootFold(), "");
327                 }
328                 
329             } finally {
330                 fh.unlock();
331             }
332         } finally {
333             ((AbstractDocument JavaDoc)doc).readUnlock();
334         }
335         changes.clear();
336         
337         
338     }
339     
340     private void dumpFolds(Fold f, String JavaDoc indent) {
341         System.out.println(indent + f);
342         for(int i = 0; i < f.getFoldCount(); i++) {
343             dumpFolds(f.getFold(i), indent+" ");
344         }
345     }
346 //
347
// private Fold getFold(FoldHierarchy fh, DocumentElement de) {
348
// int startOffset = de.getStartOffset();
349
// int endOffset = de.getEndOffset();
350
// Iterator allFolds = FoldUtilities.findRecursive(fh.getRootFold()).iterator();
351
// while(allFolds.hasNext()) {
352
// Fold f = (Fold)allFolds.next();
353
// if(debug) System.out.println("testing fold " + f);
354
// if((f.getStartOffset()) == startOffset &&
355
// ((f.getEndOffset()-1) == endOffset || f.getEndOffset() == endOffset)) {
356
// return f;
357
// }
358
// }
359
// return null;
360
// }
361

362      private Fold getFold(FoldHierarchy fh, DocumentElement de) {
363         int startOffset = de.getStartOffset();
364         int endOffset = de.getEndOffset();
365         
366         Fold f = FoldUtilities.findNearestFold(fh, de.getStartOffset());
367         //no fold found or doesn't start at the exact position
368
if(f == null || f.getStartOffset() != de.getStartOffset()) return null;
369         
370         if(f.getEndOffset() == de.getEndOffset() || (f.getEndOffset()-1) == de.getEndOffset()) return f;
371         
372         //there may be a child inside the found fold which may match the boundaries of de
373
//search them recursivelly
374
return getFold(fh, f, de);
375     }
376     
377     private Fold getFold(FoldHierarchy fh, Fold f, DocumentElement de) {
378         for (int i = 0; i < f.getFoldCount(); i++) {
379             Fold child = f.getFold(i);
380             if(child.getStartOffset() == de.getStartOffset()) {
381                 if(child.getEndOffset() == de.getEndOffset()
382                         || ((child.getEndOffset()-1) == de.getEndOffset()))
383                     return f;
384                 else
385                     return getFold(fh, child, de);
386             } else
387                 return null; //I suppose that the children are sorted so there cannot be next children with the same startoffset
388
}
389         return null;
390     }
391     
392     private boolean isOneLineElement(DocumentElement de) throws BadLocationException JavaDoc {
393         BaseDocument bdoc = (BaseDocument)de.getDocument();
394         return Utilities.getLineOffset(bdoc, de.getStartOffset()) == Utilities.getLineOffset(bdoc, de.getEndOffset());
395     }
396     
397 // private int getSetting(String settingName){
398
// JTextComponent tc = getOperation().getHierarchy().getComponent();
399
// return SettingsUtil.getInteger(org.netbeans.editor.Utilities.getKitClass(tc), settingName, JspSettings.defaultCodeFoldingUpdateInterval);
400
// }
401

402     public void settingsChange(SettingsChangeEvent evt) {
403         // Get folding presets
404
// if(evt.getSettingName() == JspSettings.CODE_FOLDING_UPDATE_TIMEOUT) {
405
// foldsUpdateInterval = getSetting(JspSettings.CODE_FOLDING_UPDATE_TIMEOUT);
406
// restartTimer();
407
// }
408
}
409     
410     private Document JavaDoc getDocument() {
411         return getOperation().getHierarchy().getComponent().getDocument();
412     }
413     
414     /** Returns a time in milliseconds for how long code folds were generated.
415      * This time doesn't involve running of any code from fold hirarchy.
416      */

417     public long getLastFoldsGenerationTime() {
418         return foldsGenerationTime;
419     }
420     
421     public void insertUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
422         //we listen only to the document model
423
}
424     
425     public void removeUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
426         //we listen only to the document model
427
}
428     
429     public void changedUpdate(DocumentEvent JavaDoc evt, FoldHierarchyTransaction transaction) {
430         //we listen only to the document model
431
}
432     
433     public void removeEmptyNotify(Fold epmtyFold) {
434     }
435     public void removeDamagedNotify(Fold damagedFold) {
436     }
437     public void expandNotify(Fold expandedFold) {
438     }
439     
440     
441     private static final class DocumentModelChangeInfo {
442         static final int ELEMENT_ADDED = 1;
443         static final int ELEMENT_REMOVED = 2;
444         
445         private DocumentElement de;
446         private int type;
447         
448         public DocumentModelChangeInfo(DocumentElement de, int changeType) {
449             this.de = de;
450             this.type = changeType;
451         }
452         public DocumentElement getDocumentElement() {
453             return de;
454         }
455         public int getChangeType() {
456             return type;
457         }
458         public String JavaDoc toString() {
459             return "" + (type == ELEMENT_ADDED ? "[ADD]" : "[REMOVE]") + " " + de;
460         }
461     }
462     
463     //enable/disable debugging messages for this class
464
private static final boolean debug = false;
465     private static final boolean lightDebug = debug || false;
466     
467 }
468
Popular Tags