1 19 20 package org.netbeans.modules.web.core.syntax.folding; 21 22 import java.lang.reflect.InvocationTargetException ; 23 import java.util.ArrayList ; 24 import java.util.Collections ; 25 import java.util.HashSet ; 26 import java.util.Hashtable ; 27 import java.util.Iterator ; 28 import java.util.List ; 29 import java.util.Stack ; 30 import java.util.Timer ; 31 import java.util.TimerTask ; 32 import javax.swing.SwingUtilities ; 33 import javax.swing.event.DocumentEvent ; 34 import javax.swing.text.BadLocationException ; 35 import javax.swing.text.Document ; 36 import javax.swing.text.JTextComponent ; 37 import org.netbeans.api.editor.fold.Fold; 38 import org.netbeans.api.editor.fold.FoldHierarchy; 39 import org.netbeans.api.editor.fold.FoldType; 40 import org.netbeans.api.editor.fold.FoldUtilities; 41 import org.netbeans.editor.BaseDocument; 42 import org.netbeans.editor.Settings; 43 import org.netbeans.editor.SettingsChangeEvent; 44 import org.netbeans.editor.SettingsChangeListener; 45 import org.netbeans.editor.SettingsUtil; 46 import org.netbeans.editor.Utilities; 47 import org.netbeans.editor.ext.html.HTMLSyntaxSupport; 48 import org.netbeans.modules.web.core.syntax.settings.JspSettings; 49 import org.netbeans.modules.web.core.syntax.JspSyntaxSupport; 50 import org.netbeans.modules.web.core.syntax.SyntaxElement; 51 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction; 52 import org.netbeans.spi.editor.fold.FoldManager; 53 import org.netbeans.spi.editor.fold.FoldOperation; 54 import org.openide.ErrorManager; 55 56 62 public class JspFoldManager implements FoldManager, SettingsChangeListener { 63 64 private static final boolean SHOW_TIMES = Boolean.getBoolean("org.netbeans.modules.web.core.folding.measure"); 65 66 private FoldOperation operation; 67 private JspSyntaxSupport sup; 68 69 private Timer timer; 71 private TimerTask timerTask; 72 private int foldsUpdateInterval = -1; 73 74 private long foldsGenerationTime = -1; 75 76 private boolean documentDirty = true; 77 78 private BaseDocument doc = null; 79 80 protected FoldOperation getOperation() { 81 return operation; 82 } 83 84 public void init(FoldOperation operation) { 85 this.operation = operation; 86 Settings.addSettingsChangeListener(this); 87 foldsUpdateInterval = getSetting(JspSettings.CODE_FOLDING_UPDATE_TIMEOUT); 88 } 89 90 public void initFolds(FoldHierarchyTransaction transaction) { 91 Document doc = getOperation().getHierarchy().getComponent().getDocument(); 93 if(doc instanceof BaseDocument) { 94 this.doc = (BaseDocument)doc; 95 96 sup = new JspSyntaxSupport(getDocument()); 97 98 timer = new Timer (); 101 restartTimer(); 102 } 103 } 104 105 private void restartTimer() { 106 documentDirty = true; 107 if(timer == null) return ; 110 111 if(timerTask != null) timerTask.cancel(); 112 timerTask = createTimerTask(); 113 timer.schedule(timerTask, foldsUpdateInterval); 114 } 115 116 private TimerTask createTimerTask() { 117 return new TimerTask () { 118 public void run() { 119 Thread thr = new Thread (new Runnable () { 121 public void run() { 122 try { 123 documentDirty = false; 124 updateFolds(); 125 }catch(ParsingCancelledException pce) { 126 if(debug) System.out.println("parsing cancelled"); 127 } 128 } 129 }); 130 thr.setPriority(Thread.MIN_PRIORITY + 1); 131 thr.start(); 132 try { 134 thr.join(); 135 }catch(InterruptedException e) { 136 } 138 } 139 }; 140 } 141 142 public void release() { 143 Settings.removeSettingsChangeListener(this); 144 if(timer != null) { 145 timer.cancel(); 146 timer = null; 147 } 148 } 149 150 public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 151 restartTimer(); 152 } 153 154 public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 155 restartTimer(); 156 } 157 158 public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 159 } 161 162 public void removeEmptyNotify(Fold epmtyFold) { 163 } 164 165 public void removeDamagedNotify(Fold damagedFold) { 166 } 167 168 public void expandNotify(Fold expandedFold) { 169 } 170 171 public void settingsChange(SettingsChangeEvent evt) { 172 if(evt.getSettingName() == JspSettings.CODE_FOLDING_UPDATE_TIMEOUT) { 174 foldsUpdateInterval = getSetting(JspSettings.CODE_FOLDING_UPDATE_TIMEOUT); 175 restartTimer(); 176 } 177 } 178 179 private List generateFolds() throws BadLocationException , ParsingCancelledException { 180 BaseDocument bdoc = (BaseDocument)getDocument(); 181 JspSyntaxSupport jspsup = (JspSyntaxSupport)bdoc.getSyntaxSupport().get(JspSyntaxSupport.class); 182 HTMLSyntaxSupport htmlsup = (HTMLSyntaxSupport)bdoc.getSyntaxSupport().get(HTMLSyntaxSupport.class); 183 184 int htmlSEEnd = -1; 185 ArrayList found = new ArrayList (getDocument().getLength() / 100); SyntaxElement sel = jspsup.getElementChain(1); 187 Stack stack = new Stack (); 188 org.netbeans.editor.ext.html.parser.SyntaxElement htmlCommentStartElement = null; 189 int prevSelOffset = sel != null ? sel.getElementOffset() : 0; 190 while(sel != null) { 191 if(documentDirty) throw new ParsingCancelledException(); 193 194 if(debug) System.out.println(sel); 195 if(sel.getCompletionContext() == JspSyntaxSupport.COMMENT_COMPLETION_CONTEXT) 196 found.add(new FoldInfo(sel.getElementOffset(), sel.getElementOffset() + sel.getElementLength(), JspFoldTypes.COMMENT, JspFoldTypes.COMMENT_DESCRIPTION)); 197 else if(sel.getCompletionContext() == JspSyntaxSupport.SCRIPTINGL_COMPLETION_CONTEXT) 198 found.add(new FoldInfo(sel.getElementOffset(), sel.getElementOffset() + sel.getElementLength(), JspFoldTypes.SCRIPTLET, JspFoldTypes.SCRIPTLET_DESCRIPTION)); 199 else if(sel.getCompletionContext() == JspSyntaxSupport.CONTENTL_COMPLETION_CONTEXT) { 200 } else if(sel.getCompletionContext() == JspSyntaxSupport.TAG_COMPLETION_CONTEXT) { 243 TagSE tse = new TagSE(sel); 245 handleOpenTagElement(tse, found, stack); 246 } else if(sel.getCompletionContext() == JspSyntaxSupport.ENDTAG_COMPLETION_CONTEXT ) { 247 TagSE tse = new TagSE(sel); 249 handleEndTagElement(tse, found, stack); 250 } 251 sel = sel.getNext(); 255 256 if(htmlSEEnd != -1 && sel != null) { 257 int psoffs = sel.getElementOffset(); 258 while(sel != null && (sel.getElementOffset() + sel.getElementLength()) <= htmlSEEnd) { 259 sel = sel.getNext(); 260 if(sel != null) { 262 if(psoffs>=sel.getElementOffset()) { 263 notifyLoop(bdoc, psoffs); 264 return Collections.EMPTY_LIST; 265 } else { 266 psoffs = sel.getElementOffset(); 267 } 268 } 269 } 270 htmlSEEnd = -1; 271 } 272 273 if(sel != null) { 275 if(prevSelOffset>=sel.getElementOffset()) { 276 notifyLoop(bdoc, prevSelOffset); 277 return Collections.EMPTY_LIST; 278 } else { 279 prevSelOffset = sel.getElementOffset(); 280 } 281 } 282 283 } 284 285 return found; 286 } 287 288 private void notifyLoop(Document doc, int offset) throws BadLocationException { 289 StringBuffer sb = new StringBuffer (); 290 sb.append("A loop in SyntaxElement-s detected around offset " + offset + " when scanning the document. Please report this and attach the dumped document content:\n"); 291 sb.append(">>>>>\n"); 292 sb.append(doc.getText(0, doc.getLength())); 293 sb.append("\n<<<<<\n"); 294 295 ErrorManager.getDefault().log(ErrorManager.WARNING, sb.toString()); } 297 298 private void handleOpenTagElement(TagSE tse, List found, Stack stack) { 299 if(tse.isSingletonTag()) { 300 found.add(new FoldInfo(tse.getElementOffset(), tse.getElementOffset() + tse.getElementLength(), JspFoldTypes.TAG, getSingletonTagFoldName(tse.getTagName()))); 302 } else { 303 stack.push(tse); 304 } 305 } 306 307 private void handleEndTagElement(TagSE tse, List found, Stack stack) { 308 if(!stack.isEmpty()) { 309 TagSE top = (TagSE)stack.peek(); 310 assert top.isOpenTag(); 311 if(tse.getTagName().equalsIgnoreCase(top.getTagName())) { 312 found.add(new FoldInfo(top.getElementOffset(), tse.getElementOffset() + tse.getElementLength(), JspFoldTypes.TAG, getTagFoldName(top.getTagName()))); 314 stack.pop(); 315 } else { 316 ArrayList savedElements = new ArrayList (); 319 boolean foundStartTag = false; 322 323 while(!stack.isEmpty()) { 324 TagSE start = (TagSE)stack.pop(); 325 savedElements.add(start); 326 assert start.isOpenTag(); 327 if(start.getTagName().equalsIgnoreCase(tse.getTagName())) { 328 found.add(new FoldInfo(start.getElementOffset(), 330 tse.getElementOffset() + tse.getElementLength(), 331 JspFoldTypes.TAG, getTagFoldName(start.getTagName()))); 332 333 foundStartTag = true; 334 break; } 336 } 337 if(!foundStartTag) { 338 for(int i = savedElements.size() - 1; i >= 0; i--) { 341 stack.push(savedElements.get(i)); 342 } 343 } 344 } 345 } 346 } 347 348 private String getSingletonTagFoldName(String tagName) { 349 StringBuffer sb = new StringBuffer (); 350 sb.append('<'); 351 sb.append(tagName); 352 sb.append('/'); 353 sb.append('>'); 354 return sb.toString(); 355 } 356 357 private String getTagFoldName(String tagName) { 358 StringBuffer sb = new StringBuffer (); 359 sb.append('<'); 360 sb.append(tagName); 361 sb.append(">...</"); 362 sb.append(tagName); 363 sb.append('>'); 364 return sb.toString(); 365 } 366 367 private synchronized void updateFolds() throws ParsingCancelledException { 368 FoldHierarchy fh = getOperation().getHierarchy(); 369 370 long startTime = System.currentTimeMillis(); 372 373 try { 374 List generated = generateFolds(); 376 377 if(SHOW_TIMES) System.out.println("[jsp folding] parsing of text of " + getDocument().getProperty(Document.TitleProperty) + " done in " + (System.currentTimeMillis() - startTime) + " millis."); 378 379 Iterator itr = generated.iterator(); 386 HashSet olfs = new HashSet (); 387 while (itr.hasNext()) { 388 FoldInfo elem = (FoldInfo) itr.next(); 389 if(isOneLineElement(elem)) olfs.add(elem); 390 } 391 generated.removeAll(olfs); 392 393 394 List existingFolds = FoldUtilities.findRecursive(fh.getRootFold()); 396 assert existingFolds != null : "Existing folds is null!"; 398 final HashSet newborns = new HashSet (generated.size() / 2); 400 final HashSet zombies = new HashSet (generated.size() / 2); 401 402 Iterator genItr = generated.iterator(); 404 Hashtable newbornsLinesCache = new Hashtable (); 405 HashSet duplicateNewborns = new HashSet (); 406 while(genItr.hasNext()) { 407 FoldInfo fi = (FoldInfo)genItr.next(); 408 if(debug) System.out.println("NEWBORN " + fi); 409 int fiLineOffset = Utilities.getLineOffset((BaseDocument)getDocument(), fi.startOffset); 411 FoldInfo found = (FoldInfo)newbornsLinesCache.get(Integer.valueOf(fiLineOffset)); 412 if(found != null) { 413 if(found.endOffset < fi.endOffset) { 415 duplicateNewborns.add(found); 417 } 418 } 419 newbornsLinesCache.put(Integer.valueOf(fiLineOffset), fi); 421 Fold fs = FoldUtilities.findNearestFold(fh, fi.startOffset); 423 if(fs != null 424 && fs.getStartOffset() == fi.startOffset 425 && fs.getEndOffset() == fi.endOffset) { 426 if(fi.foldType != fs.getType() || !(fi.description.equals(fs.getDescription()))) { 428 zombies.add(fs); 430 newborns.add(fi); 431 } 432 } else { 433 newborns.add(fi); 435 } 436 } 437 newborns.removeAll(duplicateNewborns); 438 existingFolds.removeAll(zombies); 439 440 Hashtable linesToFoldsCache = new Hashtable (); 442 Iterator extItr = existingFolds.iterator(); 444 while(extItr.hasNext()) { 445 Fold f = (Fold)extItr.next(); 446 Iterator genItr2 = generated.iterator(); 448 boolean found = false; 449 while(genItr2.hasNext()) { 450 FoldInfo fi = (FoldInfo)genItr2.next(); 451 if(f.getStartOffset() == fi.startOffset 452 && f.getEndOffset() == fi.endOffset) { 453 found = true; 454 break; 455 } 456 } 457 if(!found) { 458 zombies.add(f); 459 } else { 460 int lineoffset = Utilities.getLineOffset((BaseDocument)getDocument(), f.getStartOffset()); 462 linesToFoldsCache.put(Integer.valueOf(lineoffset), f); 463 } 464 } 466 467 Iterator newbornsItr = newborns.iterator(); 471 HashSet newbornsToRemove = new HashSet (); 472 while(newbornsItr.hasNext()) { 473 FoldInfo fi = (FoldInfo)newbornsItr.next(); 474 Fold existing = (Fold)linesToFoldsCache.get(Integer.valueOf(Utilities.getLineOffset((BaseDocument)getDocument(), fi.startOffset))); 475 if(existing != null) { 476 if(existing.getEndOffset() < fi.endOffset) { 478 zombies.add(existing); 480 } else { 481 newbornsToRemove.add(fi); 483 } 484 } 485 } 486 newborns.removeAll(newbornsToRemove); 487 488 if(SHOW_TIMES) System.out.println("[jsp folding] parsing and mangles with elements for " + getDocument().getProperty(Document.TitleProperty) + " done in " + (System.currentTimeMillis() - startTime) + " millis."); 489 490 SwingUtilities.invokeAndWait(new Runnable () { 492 public void run() { 493 if(debug) System.out.println("updating folds --> locking document!"); (getDocument()).readLock(); 496 try { 497 FoldHierarchy fh = getOperation().getHierarchy(); 499 fh.lock(); 500 try { 501 FoldHierarchyTransaction fhTran = getOperation().openTransaction(); 503 try { 504 Iterator i = zombies.iterator(); 506 while(i.hasNext()) { 507 Fold f = (Fold)i.next(); 508 if(getDocument().getLength() == 0) break; 511 512 if(debug) System.out.println("- removing fold " + f); 513 getOperation().removeFromHierarchy(f, fhTran); 514 } 515 516 Iterator newFolds = newborns.iterator(); 518 while(newFolds.hasNext()) { 519 FoldInfo f = (FoldInfo)newFolds.next(); 520 if(getDocument().getLength() == 0) break; 523 524 if(debug) System.out.println("+ adding fold " + f); 525 if(f.startOffset >= 0 526 && f.endOffset >= 0 527 && f.startOffset < f.endOffset 528 && f.endOffset <= getDocument().getLength()) { 529 getOperation().addToHierarchy(f.foldType, f.description, false, f.startOffset , f.endOffset , 0, 0, null, fhTran); 530 } 531 } 532 }catch(BadLocationException ble) { 533 Document fhDoc = getOperation().getHierarchy().getComponent().getDocument(); 535 if(fhDoc.getLength() > 0) ErrorManager.getDefault().notify(ble); 536 }finally { 537 fhTran.commit(); 538 } 539 } finally { 540 fh.unlock(); 541 } 542 } finally { 543 (getDocument()).readUnlock(); 544 } 545 if(debug) System.out.println("document unlocked!"); } 547 }); 548 549 550 }catch(BadLocationException e) { 551 Document fhDoc = getOperation().getHierarchy().getComponent().getDocument(); 555 if(fhDoc.getLength() > 0) ErrorManager.getDefault().notify(e); 556 }catch(InterruptedException ie) { 557 }catch(InvocationTargetException ite) { 559 ErrorManager.getDefault().notify(ite); 560 }catch(ParsingCancelledException pce) { 561 throw new ParsingCancelledException(); 562 }catch(Exception e) { 563 ErrorManager.getDefault().notify(e); 566 } finally { 567 if(debug) JspFoldUtils.printFolds(getOperation()); } 569 570 foldsGenerationTime = System.currentTimeMillis() - startTime; 572 if(SHOW_TIMES) System.out.println("jsp folding] folds for " + getDocument().getProperty(Document.TitleProperty) + " generated in " + foldsGenerationTime + " millis."); 573 574 } 575 576 private boolean isOneLineElement(FoldInfo fi) throws BadLocationException { 577 return Utilities.getLineOffset((BaseDocument)getDocument(), fi.startOffset) == Utilities.getLineOffset((BaseDocument)getDocument(), fi.endOffset); 578 } 579 580 private boolean foldsBoundariesEquals(Fold f1, Fold f2) { 581 return (f1.getStartOffset() == f2.getStartOffset() 582 && f1.getEndOffset() == f2.getEndOffset()); 583 } 584 585 private int getSetting(String settingName){ 586 JTextComponent tc = getOperation().getHierarchy().getComponent(); 587 return SettingsUtil.getInteger(org.netbeans.editor.Utilities.getKitClass(tc), settingName, JspSettings.defaultCodeFoldingUpdateInterval); 588 } 589 590 private BaseDocument getDocument() { 591 return this.doc; 592 } 593 594 597 public long getLastFoldsGenerationTime() { 598 return foldsGenerationTime; 599 } 600 601 private static class FoldInfo { 602 public int startOffset, endOffset; 603 public FoldType foldType = null; 604 public String description = null; 605 606 public FoldInfo(int startOffset, int endOffset, FoldType foldType, String description) { 607 this.startOffset = startOffset; 608 this.endOffset = endOffset; 609 this.foldType = foldType; 610 this.description = description; 611 } 612 613 public String toString() { 614 return "FoldInfo[start=" + startOffset + ", end=" + endOffset + ", descr=" + description + ", type=" + foldType + "]"; 615 } 616 } 617 618 private static class TagSE { 620 private final org.netbeans.editor.ext.html.parser.SyntaxElement.Named htmlse; 621 private final org.netbeans.modules.web.core.syntax.SyntaxElement.TagLikeElement jspse; 622 623 public TagSE(Object se) { 624 if(se instanceof org.netbeans.editor.ext.html.parser.SyntaxElement.Named) { 625 htmlse = (org.netbeans.editor.ext.html.parser.SyntaxElement.Named)se; 626 jspse = null; 627 } else if(se instanceof org.netbeans.modules.web.core.syntax.SyntaxElement.TagLikeElement) { 628 htmlse = null; 629 jspse = (org.netbeans.modules.web.core.syntax.SyntaxElement.TagLikeElement)se; 630 } else { 631 throw new IllegalArgumentException ("Object " + se + " is not a Tag SyntaxElement!"); 632 } 633 } 634 635 636 public boolean wrapsHTML() { 637 return htmlse != null; 638 } 639 public boolean wrapsJSP() { 640 return !wrapsHTML(); } 642 643 public int getElementOffset() { 644 return wrapsHTML() ? htmlse.getElementOffset() - 1 : jspse.getElementOffset(); 646 } 647 648 public int getElementLength() { 649 return wrapsHTML() ? htmlse.getElementLength() + 1 : jspse.getElementLength(); 651 } 652 653 public int getType() { 654 return wrapsHTML() ? htmlse.getType() : jspse.getCompletionContext(); 655 } 656 657 public boolean isOpenTag() { 658 return wrapsHTML() ? htmlse.getType() == htmlse.TYPE_TAG : jspse.getCompletionContext() == JspSyntaxSupport.TAG_COMPLETION_CONTEXT; 659 } 660 661 public String getTagName() { 662 return wrapsHTML() ? htmlse.getName() : jspse.getName(); 663 } 664 665 public boolean isSingletonTag() { 666 if(!isOpenTag()) return false; 667 return wrapsHTML() ? ((org.netbeans.editor.ext.html.parser.SyntaxElement.Tag)htmlse).isEmpty() : ((org.netbeans.modules.web.core.syntax.SyntaxElement.Tag)jspse).isClosed(); 668 } 669 670 } 671 672 private static class ParsingCancelledException extends Exception { 673 public ParsingCancelledException() { 674 super(); 675 } 676 } 677 678 private static final boolean debug = false; 680 private static final boolean lightDebug = debug || false; 681 } 682 | Popular Tags |