001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.core.output2;
043:
044: import java.util.logging.Level;
045: import java.util.logging.Logger;
046: import org.openide.util.Mutex;
047: import javax.swing.*;
048: import javax.swing.event.*;
049: import javax.swing.text.*;
050: import java.awt.event.ActionEvent;
051: import java.awt.event.ActionListener;
052: import java.util.ArrayList;
053: import java.util.Iterator;
054: import java.util.List;
055: import org.netbeans.core.output2.ui.AbstractOutputPane;
056: import org.openide.util.Exceptions;
057:
058: /** An implementation of Document directly over a memory mapped file such that
059: * no (or nearly no) memory copies are required to fetch data to display.
060: *
061: * @author Tim Boudreau, Jesse Glick
062: */
063: public class OutputDocument implements Document, Element,
064: ChangeListener, ActionListener, Runnable {
065: private List<DocumentListener> dlisteners = new ArrayList<DocumentListener>();
066: private volatile Timer timer = null;
067:
068: private OutWriter writer;
069:
070: private StringBuffer inBuffer;
071: private boolean lastInput;
072: private AbstractOutputPane pane;
073:
074: /** Creates a new instance of OutputDocument */
075: OutputDocument(OutWriter writer) {
076: if (Controller.LOG) {
077: Controller.log("Creating a Document for " + writer);
078: }
079: this .writer = writer;
080: getLines().addChangeListener(this );
081: inBuffer = new StringBuffer();
082: }
083:
084: //#119985
085: public int getOutputLength() {
086: return getLines().getCharCount();
087: }
088:
089: //#114290
090: public void setPane(AbstractOutputPane pane) {
091: this .pane = pane;
092: }
093:
094: /**
095: * Destroy this OutputDocument and its backing storage. The document should not be visible
096: * in the UI when this method is called.
097: */
098: public void dispose() {
099: if (Controller.LOG)
100: Controller
101: .log("Disposing document and backing storage for "
102: + getLines().readLock());
103: disposeQuietly();
104: writer.dispose();
105: writer = null;
106: }
107:
108: /**
109: * Destory this OutputDocument, but not its backing storage. The document should not be
110: * visible in the UI when this method is called.
111: */
112: public void disposeQuietly() {
113: if (timer != null) {
114: timer.stop();
115: timer = null;
116: }
117: dlisteners.clear();
118: lastEvent = null;
119: getLines().removeChangeListener(this );
120: }
121:
122: public synchronized void addDocumentListener(
123: DocumentListener documentListener) {
124: dlisteners.add(documentListener);
125: lastEvent = null;
126: }
127:
128: public void addUndoableEditListener(UndoableEditListener l) {
129: //do nothing
130: }
131:
132: public Position createPosition(int offset)
133: throws BadLocationException {
134: if (offset < 0
135: || offset > getLines().getCharCount()
136: + inBuffer.length()) {
137: throw new BadLocationException("Bad position", offset); //NOI18N
138: }
139: //TODO
140: return new ODPosition(offset);
141: }
142:
143: public Element getDefaultRootElement() {
144: return this ;
145: }
146:
147: public Position getEndPosition() {
148: return new ODEndPosition();
149: }
150:
151: public int getLength() {
152: return getLines().getCharCount() + inBuffer.length();
153: }
154:
155: public Object getProperty(Object obj) {
156: return null;
157: }
158:
159: public Element[] getRootElements() {
160: return new Element[] { this };
161: }
162:
163: public Position getStartPosition() {
164: return new ODStartPosition();
165: }
166:
167: public String getText(int offset, int length)
168: throws BadLocationException {
169: if (offset < 0
170: || offset > getLines().getCharCount()
171: + inBuffer.length() || length < 0) {
172: throw new BadLocationException("Bad: " + offset + "," + //NOI18N
173: length, offset);
174: }
175: if (length == 0) {
176: return ""; //NOI18N
177: }
178: String result;
179: synchronized (getLines().readLock()) {
180: int linesOffset = Math.min(getLines().getCharCount(),
181: offset);
182: int linesEnd = Math.min(getLines().getCharCount(), offset
183: + length);
184: result = getLines().getText(linesOffset, linesEnd);
185: if (offset + length > getLines().getCharCount()) {
186: int inEnd = offset + length - getLines().getCharCount();
187: result = result + inBuffer.substring(0, inEnd);
188: }
189: }
190: return result;
191: }
192:
193: private char[] reusableSubrange = new char[256];
194:
195: public void getText(int offset, int length, Segment txt)
196: throws BadLocationException {
197: if (length < 0) {
198: //document is empty
199: txt.array = new char[0];
200: txt.offset = 0;
201: txt.count = 0;
202: return;
203: }
204:
205: if (offset < 0) {
206: throw new BadLocationException("Negative offset", offset); //NOI18N
207: }
208: if (getLines().getLineCount() == 0) {
209: txt.array = new char[] { '\n' };
210: txt.offset = 0;
211: txt.count = 1;
212: return;
213: }
214: if (length > reusableSubrange.length) {
215: reusableSubrange = new char[length];
216: }
217: try {
218: synchronized (getLines().readLock()) {
219: int charCount = getLines().getCharCount();
220: int linesOffset = Math.min(charCount, offset);
221: int linesEnd = Math.min(charCount, offset + length);
222: char[] chars = getLines().getText(linesOffset,
223: linesEnd, reusableSubrange);
224: if (offset + length >= charCount) {
225: int inEnd = offset - charCount + length;
226: int inStart = Math.max(0, offset - charCount);
227: // calling Math.min to prevent nasty AOOBE wich seem to come out of nowhere..
228: inBuffer.getChars(Math.min(inStart, inBuffer
229: .length()), Math.min(inEnd, inBuffer
230: .length()), chars, linesEnd - linesOffset);
231: }
232: txt.array = chars;
233: txt.offset = 0;
234: txt.count = Math.min(length, chars.length);
235: }
236: } catch (OutOfMemoryError error) {
237: //#50189 - try to salvage what we can
238: OutWriter.lowDiskSpace = true;
239: //mkleint: is not necessary low disk space, can also mean too many mapped buffers were requirested too fast..
240: //Sets the error flag and releases the storage
241: writer.dispose();
242: Logger.getAnonymousLogger().log(Level.WARNING,
243: "OOME while reading output. Cleaning up.", //NOI18N
244: error);
245: }
246: }
247:
248: public void insertString(int offset, String str,
249: AttributeSet attributeSet) throws BadLocationException {
250: final int off = Math.max(offset, getLength()
251: - inBuffer.length());
252: final int len = str.length();
253: inBuffer.insert(off - (getLength() - inBuffer.length()), str);
254: DocumentEvent ev = new DocumentEvent() {
255: public int getOffset() {
256: return off;
257: }
258:
259: public int getLength() {
260: return len;
261: }
262:
263: public Document getDocument() {
264: return OutputDocument.this ;
265: }
266:
267: public EventType getType() {
268: return EventType.INSERT;
269: }
270:
271: public ElementChange getChange(Element arg0) {
272: return null;
273: }
274: };
275: fireDocumentEvent(ev);
276: }
277:
278: public String sendLine() {
279: final int off = getLength() - inBuffer.length();
280: final int len = inBuffer.length();
281: String toReturn = inBuffer.toString();
282: inBuffer = new StringBuffer();
283: DocumentEvent ev = new DocumentEvent() {
284: public int getOffset() {
285: return off;
286: }
287:
288: public int getLength() {
289: return len;
290: }
291:
292: public Document getDocument() {
293: return OutputDocument.this ;
294: }
295:
296: public EventType getType() {
297: return EventType.REMOVE;
298: }
299:
300: public ElementChange getChange(Element arg0) {
301: return null;
302: }
303: };
304: fireDocumentEvent(ev);
305: return toReturn;
306: }
307:
308: public void putProperty(Object obj, Object obj1) {
309: //do nothing
310: }
311:
312: public void remove(int offset, int length)
313: throws BadLocationException {
314: int startOff = getLength() - inBuffer.length();
315: final int off = Math.max(startOff, offset);
316: final int len = Math.min(length, inBuffer.length());
317: if (off - startOff + len <= getLength()) {
318: inBuffer.delete(off - startOff, off - startOff + len);
319: DocumentEvent ev = new DocumentEvent() {
320: public int getOffset() {
321: return off - len;
322: }
323:
324: public int getLength() {
325: return len;
326: }
327:
328: public Document getDocument() {
329: return OutputDocument.this ;
330: }
331:
332: public EventType getType() {
333: return EventType.REMOVE;
334: }
335:
336: public ElementChange getChange(Element arg0) {
337: return null;
338: }
339: };
340: fireDocumentEvent(ev);
341: }
342: }
343:
344: public synchronized void removeDocumentListener(
345: DocumentListener documentListener) {
346: dlisteners.remove(documentListener);
347: lastEvent = null;
348: if (dlisteners.isEmpty() && timer != null) {
349: timer.stop();
350: timer = null;
351: }
352: }
353:
354: public Lines getLines() {
355: //Unit test will check for null to determine if dispose succeeded
356: return writer != null ? writer.getLines() : null;
357: }
358:
359: public int getLineStart(int line) {
360: return getLines().getLineCount() > 0 ? getLines().getLineStart(
361: line) : 0;
362: }
363:
364: public int getLineEnd(int lineIndex) {
365: if (getLines().getLineCount() == 0) {
366: return 0;
367: }
368: int endOffset;
369: if (lineIndex >= getLines().getLineCount() - 1) {
370: endOffset = getLines().getCharCount() + inBuffer.length();
371: } else {
372: endOffset = getLines().getLineStart(lineIndex + 1);
373: }
374: return endOffset;
375: }
376:
377: public void removeUndoableEditListener(
378: UndoableEditListener undoableEditListener) {
379: //do nothing
380: }
381:
382: public void render(Runnable runnable) {
383: getElementCount(); //Force a refresh of lastPostedLine
384: runnable.run();
385: }
386:
387: public AttributeSet getAttributes() {
388: return SimpleAttributeSet.EMPTY;
389: }
390:
391: public Document getDocument() {
392: return this ;
393: }
394:
395: public Element getElement(int index) {
396: //Thanks to Mila Metelka for pointing out that Swing documents always
397: //are expected to have a trailing empty element
398: if (getLines().getLineCount() == 0) {
399: return new EmptyElement(OutputDocument.this );
400: }
401: synchronized (getLines().readLock()) {
402: if (index > lastPostedLine) {
403: lastPostedLine = index;
404: }
405: }
406: return new ODElement(index);
407: }
408:
409: public int getElementCount() {
410: int result;
411: synchronized (getLines().readLock()) {
412: result = getLines().getLineCount();
413: lastPostedLine = result;
414: }
415: if (result == 0) {
416: result = 1;
417: }
418: return result;
419: }
420:
421: public int getElementIndex(int offset) {
422: return getLines().getLineAt(offset);
423: }
424:
425: public int getEndOffset() {
426: return getLength();
427: }
428:
429: public String getName() {
430: return "foo"; //XXX
431: }
432:
433: public Element getParentElement() {
434: return null;
435: }
436:
437: public int getStartOffset() {
438: return 0;
439: }
440:
441: public boolean isLeaf() {
442: return getLines().getLineCount() == 0;
443: }
444:
445: private volatile DO lastEvent = null;
446: private int lastPostedLine = -1;
447: private int lastPostedLength = -1;
448: private int lastFiredLine = -1;
449: private int lastFiredlength = -1;
450:
451: public void stateChanged(ChangeEvent changeEvent) {
452: if (Controller.VERBOSE)
453: Controller
454: .log(changeEvent != null ? "Document got change event from writer"
455: : "Document timer polling");
456: if (dlisteners.isEmpty()) {
457: if (Controller.VERBOSE)
458: Controller.log("listeners empty, not firing");
459: return;
460: }
461: if (getLines().checkDirty(true)) {
462: if (lastEvent != null && !lastEvent.isConsumed()) {
463: if (Controller.VERBOSE)
464: Controller
465: .log("Last event not consumed, not firing");
466: return;
467: }
468: boolean noPostedLine = lastPostedLine == -1;
469:
470: int lineCount = getLines().getLineCount();
471: int size = getLines().getCharCount() + inBuffer.length();
472: lastPostedLine = lineCount;
473: lastPostedLength = size;
474:
475: if (Controller.VERBOSE)
476: Controller
477: .log("Document may fire event - last fired getLine="
478: + lastFiredLine
479: + " getLine count now "
480: + lineCount);
481: if ((lastFiredLine != lastPostedLine || lastPostedLength != lastFiredlength)
482: || noPostedLine) {
483: lastEvent = new DO(
484: Math
485: .max(
486: 0,
487: lastFiredLine == lastPostedLine ? lastFiredLine - 1
488: : lastFiredLine),
489: noPostedLine);
490: // evts.add (lastEvent);
491: Mutex.EVENT.readAccess(new Runnable() {
492: public void run() {
493: if (Controller.VERBOSE)
494: Controller
495: .log("Firing document event on EQ with start index "
496: + lastEvent.start);
497: fireDocumentEvent(lastEvent);
498: }
499: });
500: lastFiredLine = lastPostedLine;
501: lastFiredlength = lastPostedLength;
502: } else {
503: if (Controller.VERBOSE)
504: Controller.log("Line count is still " + lineCount
505: + " - not firing");
506: }
507: } else {
508: if (Controller.VERBOSE)
509: Controller
510: .log("Writer says it is not dirty, firing no change");
511: }
512: updateTimerState();
513: }
514:
515: private boolean updatingTimerState = false;
516:
517: private synchronized void updateTimerState() {
518: if (updatingTimerState) {
519: return;
520: }
521: updatingTimerState = true;
522: long newTime = System.currentTimeMillis();
523: if (timer == null && getLines().isGrowing()) {
524: if (Controller.LOG)
525: Controller.log("Starting timer");
526: //Run the timer fast and furious at first, slowing down after
527: //the initial output has been captured
528: timer = new javax.swing.Timer(50, this );
529: timer.setRepeats(true);
530: timer.start();
531: } else if (!getLines().isGrowing()) {
532: if (timer != null) {
533: timer.stop();
534: }
535: if (getLines().checkDirty(false) && timer != null) {
536: //There's still some output we haven't displayed -
537: //fire a change one last time.
538: Mutex.EVENT.readAccess(this );
539: }
540: // logInfo();
541: timer = null;
542: } else if (lastFireTime != 0 && timer != null) {
543: if (newTime - lastFireTime > 15000) {
544: //Probably we're done, but someone forgot to close the stream.
545: //Slow down the timer to a dull roar.
546: timer.setDelay(10000);
547: }
548: }
549: if (timer != null && timer.getDelay() < 350) {
550: timer.setDelay(timer.getDelay() + 20);
551: if (Controller.VERBOSE)
552: Controller.log("Decreased timer interval to "
553: + timer.getDelay());
554: }
555: lastFireTime = newTime;
556: updatingTimerState = false;
557: }
558:
559: public void run() {
560: stateChanged(null);
561: }
562:
563: private long lastFireTime = 0;
564:
565: public void actionPerformed(ActionEvent actionEvent) {
566: if (!getLines().isGrowing()) {
567: updateTimerState();
568: }
569: stateChanged(null);
570: }
571:
572: private void fireDocumentEvent(DocumentEvent de) {
573: for (DocumentListener dl : new ArrayList<DocumentListener>(
574: dlisteners)) {
575: //#114290
576: if (!(de instanceof DO)) {
577: if (pane != null) {
578: pane.doUpdateCaret();
579: }
580: }
581: if (de.getType() == DocumentEvent.EventType.REMOVE) {
582: dl.removeUpdate(de);
583: } else if (de.getType() == DocumentEvent.EventType.CHANGE) {
584: dl.changedUpdate(de);
585: } else {
586: dl.insertUpdate(de);
587: }
588: //#114290
589: if (!(de instanceof DO)) {
590: if (pane != null) {
591: pane.dontUpdateCaret();
592: }
593: }
594: }
595: }
596:
597: static final class ODPosition implements Position {
598: private int offset;
599:
600: ODPosition(int offset) {
601: this .offset = offset;
602: }
603:
604: public int getOffset() {
605: return offset;
606: }
607:
608: public int hashCode() {
609: return offset * 11;
610: }
611:
612: public boolean equals(Object o) {
613: return (o instanceof ODPosition)
614: && ((ODPosition) o).getOffset() == offset;
615: }
616: }
617:
618: final class ODEndPosition implements Position {
619: public int getOffset() {
620: return getLines().getCharCount() + inBuffer.length();
621: }
622:
623: private Document doc() {
624: return OutputDocument.this ;
625: }
626:
627: public boolean equals(Object o) {
628: return (o instanceof ODEndPosition)
629: && ((ODEndPosition) o).doc() == doc();
630: }
631:
632: public int hashCode() {
633: return -2390481;
634: }
635: }
636:
637: final class ODStartPosition implements Position {
638: public int getOffset() {
639: return 0;
640: }
641:
642: private Document doc() {
643: return OutputDocument.this ;
644: }
645:
646: public boolean equals(Object o) {
647: return (o instanceof ODStartPosition)
648: && ((ODStartPosition) o).doc() == doc();
649: }
650:
651: public int hashCode() {
652: return 2190481;
653: }
654: }
655:
656: final class ODElement implements Element {
657: private int lineIndex;
658: private int startOffset = -1;
659: private int endOffset = -1;
660:
661: ODElement(int lineIndex) {
662: this .lineIndex = lineIndex;
663: }
664:
665: public int hashCode() {
666: return lineIndex;
667: }
668:
669: public boolean equals(Object o) {
670: return (o instanceof ODElement)
671: && ((ODElement) o).lineIndex == lineIndex
672: && ((ODElement) o).getDocument() == getDocument();
673: }
674:
675: public AttributeSet getAttributes() {
676: return SimpleAttributeSet.EMPTY;
677: }
678:
679: public Document getDocument() {
680: return OutputDocument.this ;
681: }
682:
683: public Element getElement(int param) {
684: return null;
685: }
686:
687: public int getElementCount() {
688: return 0;
689: }
690:
691: public int getElementIndex(int param) {
692: return -1;
693: }
694:
695: public int getEndOffset() {
696: calc();
697: return endOffset;
698: }
699:
700: public String getName() {
701: return null;
702: }
703:
704: public Element getParentElement() {
705: return OutputDocument.this ;
706: }
707:
708: public int getStartOffset() {
709: calc();
710: return startOffset;
711: }
712:
713: void calc() {
714: synchronized (getLines().readLock()) {
715: if (startOffset == -1) {
716: startOffset = getLines().getLineCount() > 0 ? getLines()
717: .getLineStart(lineIndex)
718: : 0;
719: if (lineIndex >= getLines().getLineCount() - 1) {
720: endOffset = getLines().getCharCount()
721: + inBuffer.length();
722: } else {
723: endOffset = getLines().getLineStart(
724: lineIndex + 1);
725: }
726: assert endOffset >= getStartOffset() : "Illogical getLine #"
727: + lineIndex
728: + " with lines "
729: + getLines()
730: + " or writer has been reset";
731: } else if (lineIndex >= getLines().getLineCount() - 1) {
732: //always recalculate the last line...
733: endOffset = getLines().getCharCount()
734: + inBuffer.length();
735: }
736: }
737: }
738:
739: public boolean isLeaf() {
740: return true;
741: }
742:
743: public String toString() {
744: try {
745: return OutputDocument.this .getText(getStartOffset(),
746: getEndOffset() - getStartOffset());
747: } catch (BadLocationException ble) {
748: Exceptions.printStackTrace(ble);
749: return "";
750: }
751: }
752: }
753:
754: /**
755: * Bug in javax.swing.text.PlainView - even if the element count is 0,
756: * it tries to fetch the 0th element.
757: */
758: private static class EmptyElement implements Element {
759: private final OutputDocument doc;
760:
761: EmptyElement(OutputDocument doc) {
762: this .doc = doc;
763: }
764:
765: public javax.swing.text.AttributeSet getAttributes() {
766: return SimpleAttributeSet.EMPTY;
767: }
768:
769: public javax.swing.text.Document getDocument() {
770: return doc;
771: }
772:
773: public javax.swing.text.Element getElement(int param) {
774: return null;
775: }
776:
777: public int getElementCount() {
778: return 0;
779: }
780:
781: public int getElementIndex(int param) {
782: return 0;
783: }
784:
785: public int getEndOffset() {
786: return 0;
787: }
788:
789: public String getName() {
790: return "empty";
791: }
792:
793: public javax.swing.text.Element getParentElement() {
794: return doc;
795: }
796:
797: public int getStartOffset() {
798: return 0;
799: }
800:
801: public boolean isLeaf() {
802: return true;
803: }
804: }
805:
806: public class DO implements DocumentEvent,
807: DocumentEvent.ElementChange {
808: private int start;
809: private int offset = -1;
810: private int length = -1;
811: private int lineCount = -1;
812: private boolean consumed = false;
813: private boolean initial = false;
814: private int first = -1;
815:
816: DO(int start, boolean initial) {
817: this .start = start;
818: this .initial = initial;
819: if (start < 0) {
820: throw new IllegalArgumentException("Illogical start: "
821: + start);
822: }
823: }
824:
825: private void calc() {
826: //#60414 related assertion. The exceptions in the bug can only happen
827: // when this method is called from 2 threads? but that should not be happening
828: assert SwingUtilities.isEventDispatchThread() : "Should be accessed from AWT only or we have a synchronization problem"; //NOI18N
829: if (!consumed) {
830: // synchronized (writer) {
831: consumed = true;
832: if (Controller.VERBOSE)
833: Controller.log("EVENT CONSUMED: " + start);
834: int charsWritten = getLines().getCharCount()
835: + inBuffer.length();
836: if (initial) {
837: first = 0;
838: offset = 0;
839: lineCount = getLines().getLineCount();
840: length = charsWritten;
841: } else {
842: first = start;
843: // if (first == getLines().getLineCount()) {
844: // throw new IllegalStateException ("Out of bounds");
845: // }
846:
847: offset = getLines().getLineStart(first);
848: lineCount = getLines().getLineCount() - first;
849: length = charsWritten - offset;
850: }
851: // }
852: }
853: }
854:
855: public boolean isConsumed() {
856: return consumed;
857: }
858:
859: public String toString() {
860: boolean wasConsumed = isConsumed();
861: calc();
862: return "Event: start=" + start + " first=" + first
863: + " linecount=" + lineCount + " offset=" + offset
864: + " length=" + length + " consumed=" + wasConsumed;
865: }
866:
867: public DocumentEvent.ElementChange getChange(Element element) {
868: if (element == OutputDocument.this ) {
869: return this ;
870: } else {
871: return null;
872: }
873: }
874:
875: public Document getDocument() {
876: return OutputDocument.this ;
877: }
878:
879: public int getLength() {
880: calc();
881: return length;
882: }
883:
884: public int getOffset() {
885: calc();
886: return offset;
887: }
888:
889: public DocumentEvent.EventType getType() {
890: return start == 0 ? DocumentEvent.EventType.CHANGE
891: : DocumentEvent.EventType.INSERT;
892: }
893:
894: public Element[] getChildrenAdded() {
895: calc();
896: Element[] e = new Element[lineCount];
897: if (e.length == 0) {
898: return new Element[] { new EmptyElement(
899: OutputDocument.this ) };
900: } else {
901: for (int i = 0; i < lineCount; i++) {
902: e[i] = new ODElement(first + i);
903: if (first + i >= getLines().getLineCount()) {
904: throw new IllegalStateException("UGH!!!");
905: }
906: }
907: }
908: return e;
909: }
910:
911: public Element[] getChildrenRemoved() {
912: if (start == 0) {
913: return new Element[] { new EmptyElement(
914: OutputDocument.this ) };
915: } else {
916: return new Element[0];
917: }
918: }
919:
920: public Element getElement() {
921: return OutputDocument.this ;
922: }
923:
924: public int getIndex() {
925: calc();
926: return start;
927: }
928: }
929:
930: public String toString() {
931: return "OD@" + System.identityHashCode(this ) + " for "
932: + getLines().readLock();
933: }
934: }
|