KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > core > output2 > OutputDocument


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  * OutputDocument.java
21  *
22  * Created on March 20, 2004, 8:35 PM
23  */

24
25 package org.netbeans.core.output2;
26
27 import java.util.logging.Level JavaDoc;
28 import java.util.logging.Logger JavaDoc;
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 JavaDoc;
35 import java.awt.event.ActionListener JavaDoc;
36 import java.util.ArrayList JavaDoc;
37 import java.util.Iterator JavaDoc;
38 import java.util.List JavaDoc;
39 import org.openide.util.Exceptions;
40
41
42 /** An implementation of Document directly over a memory mapped file such that
43  * no (or nearly no) memory copies are required to fetch data to display.
44  *
45  * @author Tim Boudreau, Jesse Glick
46  */

47 class OutputDocument implements Document, Element, ChangeListener, ActionListener JavaDoc, Runnable JavaDoc {
48     private List JavaDoc dlisteners = new ArrayList JavaDoc();
49     private volatile Timer timer = null;
50
51     private OutWriter writer;
52    
53     /** Creates a new instance of OutputDocument */
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     /**
63      * Destroy this OutputDocument and its backing storage. The document should not be visible
64      * in the UI when this method is called.
65      */

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     /**
74      * Destory this OutputDocument, but not its backing storage. The document should not be
75      * visible in the UI when this method is called.
76      */

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         //do nothing
94
}
95     
96     public Position createPosition(int offset) throws BadLocationException {
97         if (offset < 0 || offset > getLines().getCharCount()) {
98             throw new BadLocationException ("Bad position", offset); //NOI18N
99
}
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 JavaDoc getProperty(Object JavaDoc 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 JavaDoc getText(int offset, int length) throws BadLocationException {
128         if (offset < 0 || offset > getLines().getCharCount() || length < 0) {
129             throw new BadLocationException ("Bad: " + offset + "," + //NOI18N
130
length, offset);
131         }
132         if (length == 0) {
133             return ""; //NOI18N
134
}
135         String JavaDoc 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             //document is empty
146
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); //NOI18N
154
}
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 JavaDoc error) {
170             //#50189 - try to salvage what we can
171
OutWriter.lowDiskSpace = true;
172             //Sets the error flag and releases the storage
173
writer.dispose();
174             Logger.getAnonymousLogger().log(Level.WARNING,
175                 "OOME while reading output. Cleaning up.", //NOI18N
176
error);
177         }
178     }
179     
180     public void insertString(int param, String JavaDoc str, AttributeSet attributeSet) throws BadLocationException {
181         throw new UnsupportedOperationException JavaDoc();
182     }
183     
184     public void putProperty(Object JavaDoc obj, Object JavaDoc obj1) {
185         //do nothing
186
}
187     
188     public void remove(int param, int param1) throws BadLocationException {
189         throw new UnsupportedOperationException JavaDoc ("Read only buffer"); //NOI18N
190
}
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         //Unit test will check for null to determine if dispose succeeded
203
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         //do nothing
225
}
226     
227     public void render(Runnable JavaDoc runnable) {
228         getElementCount(); //Force a refresh of lastPostedLine
229
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         //Thanks to Mila Metelka for pointing out that Swing documents always
242
//are expected to have a trailing empty element
243
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 JavaDoc getName() {
275         return "foo"; //XXX
276
}
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 // evts.add (lastEvent);
317
Mutex.EVENT.readAccess (new Runnable JavaDoc() {
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             //Run the timer fast and furious at first, slowing down after
344
//the initial output has been captured
345
timer = new javax.swing.Timer JavaDoc(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                 //There's still some output we haven't displayed -
354
//fire a change one last time.
355
Mutex.EVENT.readAccess(this);
356             }
357 // logInfo();
358
timer = null;
359         } else if (lastFireTime != 0 && timer != null) {
360             if (newTime - lastFireTime > 15000) {
361                 //Probably we're done, but someone forgot to close the stream.
362
//Slow down the timer to a dull roar.
363
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 JavaDoc actionEvent) {
380         if (!getLines().isGrowing()) {
381             updateTimerState();
382         }
383         stateChanged(null);
384     }
385     
386     private void fireDocumentEvent (DocumentEvent de) {
387         Iterator JavaDoc i = new ArrayList JavaDoc(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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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                     //always recalculate the last line...
521
endOffset = getLines().getCharCount();
522                 }
523             }
524         }
525         
526         public boolean isLeaf() {
527             return true;
528         }
529         
530         public String JavaDoc 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     /**
542      * Bug in javax.swing.text.PlainView - even if the element count is 0,
543      * it tries to fetch the 0th element.
544      */

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 JavaDoc getAttributes() {
553             return SimpleAttributeSet.EMPTY;
554         }
555         
556         public javax.swing.text.Document JavaDoc getDocument() {
557             return doc;
558         }
559         
560         public javax.swing.text.Element JavaDoc 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 JavaDoc getName() {
577             return "empty";
578         }
579         
580         public javax.swing.text.Element JavaDoc 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 JavaDoc ("Illogical start: " + start);
606             }
607         }
608         
609         private void calc() {
610             //#60414 related assertion. The exceptions in the bug can only happen
611
// when this method is called from 2 threads? but that should not be happening
612
assert SwingUtilities.isEventDispatchThread() : "Should be accessed from AWT only or we have a synchronization problem"; //NOI18N
613
if (!consumed) {
614 // synchronized (writer) {
615
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 // if (first == getLines().getLineCount()) {
626
// throw new IllegalStateException ("Out of bounds");
627
// }
628

629                         offset = getLines().getLineStart(first);
630                         lineCount = getLines().getLineCount() - first;
631                         length = charsWritten - offset;
632                     }
633 // }
634
}
635         }
636         
637         public boolean isConsumed() {
638             return consumed;
639         }
640         
641         public String JavaDoc 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 JavaDoc ("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 JavaDoc toString() {
709         return "OD@" + System.identityHashCode(this) + " for " + getLines().readLock();
710     }
711 }
712
Popular Tags