1 19 24 25 package org.netbeans.core.output2; 26 27 import java.util.logging.Level ; 28 import java.util.logging.Logger ; 29 import org.openide.util.Mutex; 30 31 import javax.swing.*; 32 import javax.swing.event.*; 33 import javax.swing.text.*; 34 import java.awt.event.ActionEvent ; 35 import java.awt.event.ActionListener ; 36 import java.util.ArrayList ; 37 import java.util.Iterator ; 38 import java.util.List ; 39 import org.openide.util.Exceptions; 40 41 42 47 class OutputDocument implements Document, Element, ChangeListener, ActionListener , Runnable { 48 private List dlisteners = new ArrayList (); 49 private volatile Timer timer = null; 50 51 private OutWriter writer; 52 53 54 OutputDocument(OutWriter writer) { 55 if (Controller.LOG) { 56 Controller.log ("Creating a Document for " + writer); 57 } 58 this.writer = writer; 59 getLines().addChangeListener(this); 60 } 61 62 66 public void dispose() { 67 if (Controller.LOG) Controller.log ("Disposing document and backing storage for " + getLines().readLock()); 68 disposeQuietly(); 69 writer.dispose(); 70 writer = null; 71 } 72 73 77 public void disposeQuietly() { 78 if (timer != null) { 79 timer.stop(); 80 timer = null; 81 } 82 dlisteners.clear(); 83 lastEvent = null; 84 getLines().removeChangeListener(this); 85 } 86 87 public synchronized void addDocumentListener(DocumentListener documentListener) { 88 dlisteners.add (documentListener); 89 lastEvent = null; 90 } 91 92 public void addUndoableEditListener(UndoableEditListener l) { 93 } 95 96 public Position createPosition(int offset) throws BadLocationException { 97 if (offset < 0 || offset > getLines().getCharCount()) { 98 throw new BadLocationException ("Bad position", offset); } 100 return new ODPosition (offset); 101 } 102 103 public Element getDefaultRootElement() { 104 return this; 105 } 106 107 public Position getEndPosition() { 108 return new ODEndPosition(); 109 } 110 111 public int getLength() { 112 return getLines().getCharCount(); 113 } 114 115 public Object getProperty(Object obj) { 116 return null; 117 } 118 119 public Element[] getRootElements() { 120 return new Element[] {this}; 121 } 122 123 public Position getStartPosition() { 124 return new ODStartPosition(); 125 } 126 127 public String getText(int offset, int length) throws BadLocationException { 128 if (offset < 0 || offset > getLines().getCharCount() || length < 0) { 129 throw new BadLocationException ("Bad: " + offset + "," + length, offset); 131 } 132 if (length == 0) { 133 return ""; } 135 String result; 136 synchronized (getLines().readLock()) { 137 result = getLines().getText(offset,offset + length); 138 } 139 return result; 140 } 141 142 private char[] reusableSubrange = new char [256]; 143 public void getText(int offset, int length, Segment txt) throws BadLocationException { 144 if (length < 0) { 145 txt.array = new char[0]; 147 txt.offset=0; 148 txt.count = 0; 149 return; 150 } 151 152 if (offset < 0) { 153 throw new BadLocationException ("Negative offset", offset); } 155 if (getLines().getLineCount() == 0) { 156 txt.array = new char[] {'\n'}; 157 txt.offset = 0; 158 txt.count = 1; 159 return; 160 } 161 if (length > reusableSubrange.length) { 162 reusableSubrange = new char[length]; 163 } 164 try { 165 char[] chars = getLines().getText(offset, offset + length, reusableSubrange); 166 txt.array = chars; 167 txt.offset = 0; 168 txt.count = Math.min(length, chars.length); 169 } catch (OutOfMemoryError error) { 170 OutWriter.lowDiskSpace = true; 172 writer.dispose(); 174 Logger.getAnonymousLogger().log(Level.WARNING, 175 "OOME while reading output. Cleaning up.", error); 177 } 178 } 179 180 public void insertString(int param, String str, AttributeSet attributeSet) throws BadLocationException { 181 throw new UnsupportedOperationException (); 182 } 183 184 public void putProperty(Object obj, Object obj1) { 185 } 187 188 public void remove(int param, int param1) throws BadLocationException { 189 throw new UnsupportedOperationException ("Read only buffer"); } 191 192 public synchronized void removeDocumentListener(DocumentListener documentListener) { 193 dlisteners.remove(documentListener); 194 lastEvent = null; 195 if (dlisteners.isEmpty() && timer != null) { 196 timer.stop(); 197 timer = null; 198 } 199 } 200 201 public Lines getLines() { 202 return writer != null ? writer.getLines() : null; 204 } 205 206 public int getLineStart (int line) { 207 return getLines().getLineCount() > 0 ? getLines().getLineStart(line) : 0; 208 } 209 210 public int getLineEnd (int lineIndex) { 211 if (getLines().getLineCount() == 0) { 212 return 0; 213 } 214 int endOffset; 215 if (lineIndex >= getLines().getLineCount()-1) { 216 endOffset = getLines().getCharCount(); 217 } else { 218 endOffset = getLines().getLineStart(lineIndex+1); 219 } 220 return endOffset; 221 } 222 223 public void removeUndoableEditListener(UndoableEditListener undoableEditListener) { 224 } 226 227 public void render(Runnable runnable) { 228 getElementCount(); runnable.run(); 230 } 231 232 public AttributeSet getAttributes() { 233 return SimpleAttributeSet.EMPTY; 234 } 235 236 public Document getDocument() { 237 return this; 238 } 239 240 public Element getElement(int index) { 241 if (getLines().getLineCount() == 0) { 244 return new EmptyElement(OutputDocument.this); 245 } 246 synchronized (getLines().readLock()) { 247 if (index > lastPostedLine) { 248 lastPostedLine = index; 249 } 250 } 251 return new ODElement (index); 252 } 253 254 public int getElementCount() { 255 int result; 256 synchronized (getLines().readLock()) { 257 result = getLines().getLineCount(); 258 lastPostedLine = result; 259 } 260 if (result == 0) { 261 result = 1; 262 } 263 return result; 264 } 265 266 public int getElementIndex(int offset) { 267 return getLines().getLineAt(offset); 268 } 269 270 public int getEndOffset() { 271 return getLength(); 272 } 273 274 public String getName() { 275 return "foo"; } 277 278 public Element getParentElement() { 279 return null; 280 } 281 282 public int getStartOffset() { 283 return 0; 284 } 285 286 public boolean isLeaf() { 287 return getLines().getLineCount() == 0; 288 } 289 290 private volatile DO lastEvent = null; 291 private int lastPostedLine = -1; 292 private int lastPostedLength = -1; 293 private int lastFiredLine = -1; 294 private int lastFiredlength = -1; 295 public void stateChanged(ChangeEvent changeEvent) { 296 if (Controller.VERBOSE) Controller.log (changeEvent != null ? "Document got change event from writer" : "Document timer polling"); 297 if (dlisteners.isEmpty()) { 298 if (Controller.VERBOSE) Controller.log ("listeners empty, not firing"); 299 return; 300 } 301 if (getLines().checkDirty(true)) { 302 if (lastEvent != null && !lastEvent.isConsumed()) { 303 if (Controller.VERBOSE) Controller.log ("Last event not consumed, not firing"); 304 return; 305 } 306 boolean noPostedLine = lastPostedLine == - 1; 307 308 int lineCount = getLines().getLineCount(); 309 int size = getLines().getCharCount(); 310 lastPostedLine = lineCount; 311 lastPostedLength = size; 312 313 if (Controller.VERBOSE) Controller.log ("Document may fire event - last fired getLine=" + lastFiredLine + " getLine count now " + lineCount); 314 if ((lastFiredLine != lastPostedLine || lastPostedLength != lastFiredlength) || noPostedLine) { 315 lastEvent = new DO(Math.max(0, lastFiredLine == lastPostedLine ? lastFiredLine - 1 : lastFiredLine), noPostedLine); 316 Mutex.EVENT.readAccess (new Runnable () { 318 public void run() { 319 if (Controller.VERBOSE) Controller.log("Firing document event on EQ with start index " + lastEvent.start); 320 fireDocumentEvent (lastEvent); 321 } 322 }); 323 lastFiredLine = lastPostedLine; 324 lastFiredlength = lastPostedLength; 325 } else { 326 if (Controller.VERBOSE) Controller.log ("Line count is still " + lineCount + " - not firing"); 327 } 328 } else { 329 if (Controller.VERBOSE) Controller.log ("Writer says it is not dirty, firing no change"); 330 } 331 updateTimerState(); 332 } 333 334 private boolean updatingTimerState = false; 335 private synchronized void updateTimerState() { 336 if (updatingTimerState) { 337 return; 338 } 339 updatingTimerState = true; 340 long newTime = System.currentTimeMillis(); 341 if (timer == null && getLines().isGrowing()) { 342 if (Controller.LOG) Controller.log("Starting timer"); 343 timer = new javax.swing.Timer (50, this); 346 timer.setRepeats(true); 347 timer.start(); 348 } else if (!getLines().isGrowing()) { 349 if (timer != null) { 350 timer.stop(); 351 } 352 if (getLines().checkDirty(false) && timer != null) { 353 Mutex.EVENT.readAccess(this); 356 } 357 timer = null; 359 } else if (lastFireTime != 0 && timer != null) { 360 if (newTime - lastFireTime > 15000) { 361 timer.setDelay (10000); 364 } 365 } 366 if (timer != null && timer.getDelay() < 350) { 367 timer.setDelay (timer.getDelay() + 20); 368 if (Controller.VERBOSE) Controller.log ("Decreased timer interval to " + timer.getDelay()); 369 } 370 lastFireTime = newTime; 371 updatingTimerState = false; 372 } 373 374 public void run() { 375 stateChanged(null); 376 } 377 378 private long lastFireTime = 0; 379 public void actionPerformed(ActionEvent actionEvent) { 380 if (!getLines().isGrowing()) { 381 updateTimerState(); 382 } 383 stateChanged(null); 384 } 385 386 private void fireDocumentEvent (DocumentEvent de) { 387 Iterator i = new ArrayList (dlisteners).iterator(); 388 while (i.hasNext()) { 389 DocumentListener dl = (DocumentListener) i.next(); 390 dl.insertUpdate(de); 391 } 392 } 393 394 static final class ODPosition implements Position { 395 private int offset; 396 397 ODPosition (int offset) { 398 this.offset = offset; 399 } 400 401 public int getOffset() { 402 return offset; 403 } 404 405 public int hashCode() { 406 return offset * 11; 407 } 408 409 public boolean equals (Object o) { 410 return (o instanceof ODPosition) && 411 ((ODPosition) o).getOffset() == offset; 412 } 413 } 414 415 final class ODEndPosition implements Position { 416 public int getOffset() { 417 return getLines().getCharCount(); 418 } 419 420 private Document doc() { 421 return OutputDocument.this; 422 } 423 424 public boolean equals (Object o) { 425 return (o instanceof ODEndPosition) && ((ODEndPosition) o).doc() == 426 doc(); 427 } 428 429 public int hashCode() { 430 return -2390481; 431 } 432 } 433 434 final class ODStartPosition implements Position { 435 public int getOffset() { 436 return 0; 437 } 438 439 private Document doc() { 440 return OutputDocument.this; 441 } 442 443 public boolean equals (Object o) { 444 return (o instanceof ODStartPosition) && ((ODStartPosition) o).doc() == 445 doc(); 446 } 447 448 public int hashCode() { 449 return 2190481; 450 } 451 } 452 453 final class ODElement implements Element { 454 private int lineIndex; 455 private int startOffset = -1; 456 private int endOffset = -1; 457 ODElement (int lineIndex) { 458 this.lineIndex = lineIndex; 459 } 460 461 public int hashCode() { 462 return lineIndex; 463 } 464 465 public boolean equals (Object o) { 466 return (o instanceof ODElement) && ((ODElement) o).lineIndex == lineIndex && 467 ((ODElement) o).getDocument() == getDocument(); 468 } 469 470 public AttributeSet getAttributes() { 471 return SimpleAttributeSet.EMPTY; 472 } 473 474 public Document getDocument() { 475 return OutputDocument.this; 476 } 477 478 public Element getElement(int param) { 479 return null; 480 } 481 482 public int getElementCount() { 483 return 0; 484 } 485 486 public int getElementIndex(int param) { 487 return -1; 488 } 489 490 public int getEndOffset() { 491 calc(); 492 return endOffset; 493 } 494 495 public String getName() { 496 return null; 497 } 498 499 public Element getParentElement() { 500 return OutputDocument.this; 501 } 502 503 public int getStartOffset() { 504 calc(); 505 return startOffset; 506 } 507 508 void calc() { 509 synchronized (getLines().readLock()) { 510 if (startOffset == -1) { 511 startOffset = getLines().getLineCount() > 0 ? getLines().getLineStart(lineIndex) : 0; 512 if (lineIndex >= getLines().getLineCount()-1) { 513 endOffset = getLines().getCharCount(); 514 } else { 515 endOffset = getLines().getLineStart(lineIndex+1); 516 } 517 assert endOffset >= getStartOffset() : "Illogical getLine #" + lineIndex 518 + " with lines " + getLines() + " or writer has been reset"; 519 } else if (lineIndex >= getLines().getLineCount()-1) { 520 endOffset = getLines().getCharCount(); 522 } 523 } 524 } 525 526 public boolean isLeaf() { 527 return true; 528 } 529 530 public String toString() { 531 try { 532 return OutputDocument.this.getText(getStartOffset(), getEndOffset() 533 - getStartOffset()); 534 } catch (BadLocationException ble) { 535 Exceptions.printStackTrace(ble); 536 return ""; 537 } 538 } 539 } 540 541 545 private static class EmptyElement implements Element { 546 private final OutputDocument doc; 547 548 EmptyElement (OutputDocument doc) { 549 this.doc = doc; 550 } 551 552 public javax.swing.text.AttributeSet getAttributes() { 553 return SimpleAttributeSet.EMPTY; 554 } 555 556 public javax.swing.text.Document getDocument() { 557 return doc; 558 } 559 560 public javax.swing.text.Element getElement(int param) { 561 return null; 562 } 563 564 public int getElementCount() { 565 return 0; 566 } 567 568 public int getElementIndex(int param) { 569 return 0; 570 } 571 572 public int getEndOffset() { 573 return 0; 574 } 575 576 public String getName() { 577 return "empty"; 578 } 579 580 public javax.swing.text.Element getParentElement() { 581 return doc; 582 } 583 584 public int getStartOffset() { 585 return 0; 586 } 587 588 public boolean isLeaf() { 589 return true; 590 } 591 } 592 593 private class DO implements DocumentEvent, DocumentEvent.ElementChange { 594 private int start; 595 private int offset = -1; 596 private int length = -1; 597 private int lineCount = -1; 598 private boolean consumed = false; 599 private boolean initial = false; 600 private int first = -1; 601 DO (int start, boolean initial) { 602 this.start = start; 603 this.initial = initial; 604 if (start < 0) { 605 throw new IllegalArgumentException ("Illogical start: " + start); 606 } 607 } 608 609 private void calc() { 610 assert SwingUtilities.isEventDispatchThread() : "Should be accessed from AWT only or we have a synchronization problem"; if (!consumed) { 614 consumed = true; 616 if (Controller.VERBOSE) Controller.log ("EVENT CONSUMED: " + start); 617 int charsWritten = getLines().getCharCount(); 618 if (initial) { 619 first = 0; 620 offset = 0; 621 lineCount = getLines().getLineCount(); 622 length = charsWritten; 623 } else { 624 first = start; 625 629 offset = getLines().getLineStart(first); 630 lineCount = getLines().getLineCount() - first; 631 length = charsWritten - offset; 632 } 633 } 635 } 636 637 public boolean isConsumed() { 638 return consumed; 639 } 640 641 public String toString() { 642 boolean wasConsumed = isConsumed(); 643 calc(); 644 return "Event: start=" + start + " first=" + first + " linecount=" + lineCount + " offset=" + offset + " length=" + length + " consumed=" + wasConsumed; 645 } 646 647 public DocumentEvent.ElementChange getChange(Element element) { 648 if (element == OutputDocument.this) { 649 return this; 650 } else { 651 return null; 652 } 653 } 654 655 public Document getDocument() { 656 return OutputDocument.this; 657 } 658 659 public int getLength() { 660 calc(); 661 return length; 662 } 663 664 public int getOffset() { 665 calc(); 666 return offset; 667 } 668 669 public DocumentEvent.EventType getType() { 670 return start == 0 ? DocumentEvent.EventType.CHANGE : 671 DocumentEvent.EventType.INSERT; 672 } 673 674 public Element[] getChildrenAdded() { 675 calc(); 676 Element[] e = new Element[lineCount]; 677 if (e.length == 0) { 678 return new Element[] { new EmptyElement(OutputDocument.this) }; 679 } else { 680 for (int i=0; i < lineCount; i++) { 681 e[i] = new ODElement(first + i); 682 if (first + i >= getLines().getLineCount()) { 683 throw new IllegalStateException ("UGH!!!"); 684 } 685 } 686 } 687 return e; 688 } 689 690 public Element[] getChildrenRemoved() { 691 if (start == 0) { 692 return new Element[] { new EmptyElement(OutputDocument.this) }; 693 } else { 694 return new Element[0]; 695 } 696 } 697 698 public Element getElement() { 699 return OutputDocument.this; 700 } 701 702 public int getIndex() { 703 calc(); 704 return start; 705 } 706 } 707 708 public String toString() { 709 return "OD@" + System.identityHashCode(this) + " for " + getLines().readLock(); 710 } 711 } 712 | Popular Tags |