1 19 20 package org.netbeans.modules.xml.text.folding; 21 import java.util.Iterator ; 22 import java.util.Timer ; 23 import java.util.TimerTask ; 24 import java.util.Vector ; 25 import javax.swing.event.DocumentEvent ; 26 import javax.swing.text.AbstractDocument ; 27 import javax.swing.text.BadLocationException ; 28 import javax.swing.text.Document ; 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 56 public class XmlFoldManager implements FoldManager, SettingsChangeListener, DocumentModelListener { 57 58 private FoldOperation operation; 59 60 private Timer timer; 62 private TimerTask timerTask; 63 64 private int foldsUpdateInterval = 500; 65 private long foldsGenerationTime = -1; 66 67 private DocumentModel model = null; 68 69 private Vector changes = new Vector (); 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 } 81 82 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 if(!(getDocument() instanceof BaseDocument)) return ; 101 102 if(getDocument().getLength() > 0) { 104 timer = new Timer (); 107 restartTimer(); 108 } 109 } 110 111 112 private void initModelAndFolds() { 115 try { 116 model = DocumentModel.getDocumentModel((BaseDocument)getDocument()); 117 model.addDocumentModelListener(this); 119 addElementsRecursivelly(changes, model.getRootElement()); 122 } catch (DocumentModelException e) { 123 ErrorManager.getDefault().notify(e); 124 } 125 } 126 127 private void addElementsRecursivelly(Vector changes, DocumentElement de) { 128 try { 130 if(!de.equals(model.getRootElement()) && !isOneLineElement(de)) changes.add(new DocumentModelChangeInfo(de, DocumentModelChangeInfo.ELEMENT_ADDED)); 131 }catch(BadLocationException e) { 132 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 133 } 134 Iterator 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 DocumentElement tested = removed ? model.getLeafElementForOffset(de.getStartOffset()) : de; 165 boolean restartTimer = false; 166 while(tested != null) { 167 if(tested.equals(model.getRootElement())) break ; 169 170 if(!tested.getType().equals(XMLDocumentModelProvider.XML_CONTENT)) { 171 172 try { 174 Fold existingFold = getFold(getOperation().getHierarchy(), tested); 175 boolean oneLineElement = isOneLineElement(tested); 176 if(existingFold != null && oneLineElement) { 177 changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_REMOVED)); 181 restartTimer = true; 182 } 183 if(existingFold == null && !oneLineElement) { 184 changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_ADDED)); 187 restartTimer = true; 188 } 189 if(existingFold != null && !oneLineElement) { 190 if(getFoldTypeForElement(tested) != existingFold.getType() 193 || !existingFold.getDescription().equals("<"+tested.getName()+">")) { 194 changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_REMOVED)); 196 changes.add(new DocumentModelChangeInfo(tested, DocumentModelChangeInfo.ELEMENT_ADDED)); 197 } 198 } 199 200 }catch(BadLocationException e) { 201 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 202 } 203 } 204 205 tested = tested.getParentElement(); } 207 208 if(restartTimer) restartTimer(); } 210 211 private FoldType getFoldTypeForElement(DocumentElement de) { 212 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 } 231 232 private void restartTimer() { 233 if(timer == null) return ; 236 237 if(timerTask != null) timerTask.cancel(); 238 timerTask = createTimerTask(); 239 timer.schedule(timerTask, foldsUpdateInterval); 240 } 241 242 private TimerTask createTimerTask() { 243 return new TimerTask () { 244 public void run() { 245 try { 246 if(model == null) initModelAndFolds(); 247 updateFolds(); 248 }catch(Exception e) { 249 ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); 251 } 252 } 253 }; 254 } 255 256 258 259 private void updateFolds() { 260 Document doc = getDocument(); 261 if(!(doc instanceof AbstractDocument )) return ; 262 263 ((AbstractDocument )doc).readLock(); 264 try { 265 FoldHierarchy fh = getOperation().getHierarchy(); 266 fh.lock(); 267 try { 268 FoldHierarchyTransaction fhTran = getOperation().openTransaction(); 269 try { 270 Iterator changesItr = ((Vector )changes.clone()).iterator(); 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 foldName = ""; 279 FoldType type = XmlFoldTypes.TEXT; 281 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"); type = XmlFoldTypes.PI; 289 } else if(de.getType().equals(XMLDocumentModelProvider.XML_DOCTYPE)) { 290 foldName = NbBundle.getMessage(XmlFoldManager.class, "LBL_DOCTYPE"); type = XmlFoldTypes.DOCTYPE; 292 } else if(de.getType().equals(XMLDocumentModelProvider.XML_COMMENT)) { 293 foldName = NbBundle.getMessage(XmlFoldManager.class, "LBL_COMMENT"); type = XmlFoldTypes.COMMENT; 295 } else if(de.getType().equals(XMLDocumentModelProvider.XML_CDATA)) { 296 foldName = NbBundle.getMessage(XmlFoldManager.class, "LBL_CDATA"); type = XmlFoldTypes.CDATA; 298 } 299 if(getFold(fh, de) == null) { 300 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 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 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 )doc).readUnlock(); 334 } 335 changes.clear(); 336 337 338 } 339 340 private void dumpFolds(Fold f, String 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 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 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 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; } 389 return null; 390 } 391 392 private boolean isOneLineElement(DocumentElement de) throws BadLocationException { 393 BaseDocument bdoc = (BaseDocument)de.getDocument(); 394 return Utilities.getLineOffset(bdoc, de.getStartOffset()) == Utilities.getLineOffset(bdoc, de.getEndOffset()); 395 } 396 397 402 public void settingsChange(SettingsChangeEvent evt) { 403 } 409 410 private Document getDocument() { 411 return getOperation().getHierarchy().getComponent().getDocument(); 412 } 413 414 417 public long getLastFoldsGenerationTime() { 418 return foldsGenerationTime; 419 } 420 421 public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 422 } 424 425 public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 426 } 428 429 public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 430 } 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 toString() { 459 return "" + (type == ELEMENT_ADDED ? "[ADD]" : "[REMOVE]") + " " + de; 460 } 461 } 462 463 private static final boolean debug = false; 465 private static final boolean lightDebug = debug || false; 466 467 } 468 | Popular Tags |