KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > spi > editor > highlighting > support > OffsetsBag


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 package org.netbeans.spi.editor.highlighting.support;
21
22 import java.lang.ref.WeakReference JavaDoc;
23 import java.util.ConcurrentModificationException JavaDoc;
24 import java.util.NoSuchElementException JavaDoc;
25 import java.util.logging.Level JavaDoc;
26 import java.util.logging.Logger JavaDoc;
27 import javax.swing.event.DocumentEvent JavaDoc;
28 import javax.swing.event.DocumentListener JavaDoc;
29 import javax.swing.text.AttributeSet JavaDoc;
30 import javax.swing.text.Document JavaDoc;
31 import org.netbeans.api.editor.settings.AttributesUtilities;
32 import org.netbeans.modules.editor.lib2.highlighting.OffsetGapList;
33 import org.netbeans.spi.editor.highlighting.*;
34 import org.openide.util.Utilities;
35
36 /**
37  * A bag of highlighted areas specified by their offsets in a document.
38  *
39  * <p>The highlighted areas (highlights) are determined by their starting and ending
40  * offsets in a document and the set of attributes that should be used for rendering
41  * that area. The highlights can be arbitrarily added to and remove from this bag.
42  *
43  * <p>The <code>OffsetsBag</code> is designed to work over a single
44  * document, which is passed in to the constructor. All offsets
45  * accepted by various methods in this class must refer to positions within
46  * this document. Therefore any offsets passed in to the methods in this class
47  * have to be equal to or greater than zero and less than or equal to the document
48  * size.
49  *
50  * <p>The <code>OffsetsBag</code> can operate in two modes depending on a
51  * value passed in its construtor. The mode determines how the bag will treat
52  * newly added highlights that overlap with existing highlights in the bag.
53  *
54  * <p><b>Trimming mode</b>:
55  * In the trimming mode the bag will <i>trim</i> any existing highlights that
56  * would overlap with a newly added highlight. In this mode the newly
57  * added highlights always replace the existing highlights if they overlap.
58  *
59  * <p><b>Merging mode</b>:
60  * In the merging mode the bag will <i>merge</i> attributes of the overlapping highlights.
61  * The area where the new highlight overlaps with some existing highlights will
62  * then constitute a new highlight, which attributes will be a composition of
63  * the attributes of both the new and existing highlight. Should there be attributes
64  * with the same name the attribute values from the newly added highlight will take
65  * precedence.
66  *
67  * @author Vita Stejskal
68  */

69 public final class OffsetsBag extends AbstractHighlightsContainer {
70
71     private static final Logger JavaDoc LOG = Logger.getLogger(OffsetsBag.class.getName());
72
73     private Document JavaDoc document;
74     private OffsetGapList<Mark> marks;
75     private boolean mergeHighlights;
76     private long version = 0;
77     private DocL docListener;
78     
79     /**
80      * Creates a new instance of <code>OffsetsBag</code>, which trims highlights
81      * as they are added. It calls the {@link #OffsetsBag(Document, boolean)} constructor
82      * passing <code>false</code> as a parameter.
83      *
84      * @param document The document that should be highlighted.
85      */

86     public OffsetsBag(Document JavaDoc document) {
87         this(document, false);
88     }
89     
90     /**
91      * Creates a new instance of <code>OffsetsBag</code>.
92      *
93      * @param document The document that should be highlighted.
94      * @param mergeHighlights Determines whether highlights should be merged
95      * or trimmed.
96      */

97     public OffsetsBag(Document JavaDoc document, boolean mergeHighlights) {
98         this.document = document;
99         this.mergeHighlights = mergeHighlights;
100         this.marks = new OffsetGapList<Mark>();
101         this.docListener = new DocL(this);
102         this.document.addDocumentListener(docListener);
103     }
104     
105     /**
106      * Adds a highlight to this bag. The highlight is specified by its staring
107      * and ending offset and by its attributes. Adding a highlight that overlaps
108      * with one or more existing highlights can have a different result depending
109      * on the value of the <code>mergingHighlights</code> parameter used for
110      * constructing this bag.
111      *
112      * @param startOffset The beginning of the highlighted area.
113      * @param endOffset The end of the highlighted area.
114      * @param attributes The attributes to use for highlighting.
115      */

116     public void addHighlight(int startOffset, int endOffset, AttributeSet JavaDoc attributes) {
117         int [] offsets;
118         
119         synchronized (marks) {
120             offsets = addHighlightImpl(startOffset, endOffset, attributes);
121             if (offsets != null) {
122                 version++;
123             }
124         }
125         
126         if (offsets != null) {
127             fireHighlightsChange(offsets[0], offsets[1]);
128         }
129     }
130     
131     /**
132      * Adds all highlights from the bag passed in. This method is equivalent
133      * to calling <code>addHighlight</code> for all the highlights in the
134      * <code>bag</code> except that the changes are done atomically.
135      *
136      * @param bag The bag of highlights that will be atomically
137      * added to this bag.
138      */

139     public void addAllHighlights(HighlightsSequence bag) {
140         int [] offsets;
141         
142         synchronized (marks) {
143             offsets = addAllHighlightsImpl(bag);
144             if (offsets != null) {
145                 version++;
146             }
147         }
148         
149         if (offsets != null) {
150             fireHighlightsChange(offsets[0], offsets[1]);
151         }
152     }
153
154     /**
155      * Resets this bag to use the new set of highlights. This method drops
156      * all the existing highlights in this bag and adds all highlights from
157      * the sequence passed in as a parameter. The changes are made atomically.
158      * The sequence passed in has to be acting on the same <code>Document</code>
159      * as this bag.
160      *
161      * @param seq New highlights to add.
162      */

163     public void setHighlights(HighlightsSequence seq) {
164         if (seq instanceof Seq) {
165             setHighlights(((Seq) seq).getBag());
166             return;
167         }
168         
169         int changeStart = Integer.MAX_VALUE;
170         int changeEnd = Integer.MIN_VALUE;
171         
172         synchronized (marks) {
173             int [] clearedArea = clearImpl();
174             int [] populatedArea = addAllHighlightsImpl(seq);
175         
176             if (clearedArea != null) {
177                 changeStart = clearedArea[0];
178                 changeEnd = clearedArea[1];
179             }
180
181             if (populatedArea != null) {
182                 if (changeStart == Integer.MAX_VALUE || changeStart > populatedArea[0]) {
183                     changeStart = populatedArea[0];
184                 }
185                 if (changeEnd == Integer.MIN_VALUE || changeEnd < populatedArea[1]) {
186                     changeEnd = populatedArea[1];
187                 }
188             }
189             
190             if (changeStart < changeEnd) {
191                 version++;
192             }
193         }
194         
195         if (changeStart < changeEnd) {
196             fireHighlightsChange(changeStart, changeEnd);
197         }
198     }
199
200     /**
201      * Resets this bag to use the new set of highlights. This method drops
202      * all the existing highlights in this bag and adds all highlights from
203      * the bag passed in as a parameter. The changes are made atomically. Both
204      * bags have to be acting on the same <code>Document</code>.
205      *
206      * @param bag New highlights to add.
207      */

208     public void setHighlights(OffsetsBag bag) {
209         int changeStart = Integer.MAX_VALUE;
210         int changeEnd = Integer.MIN_VALUE;
211
212         synchronized (marks) {
213             int [] clearedArea = clearImpl();
214             int [] populatedArea = null;
215             
216             OffsetGapList<OffsetsBag.Mark> newMarks = bag.getMarks();
217
218             synchronized (newMarks) {
219                 for(OffsetsBag.Mark mark : newMarks) {
220                     marks.add(marks.size(), new OffsetsBag.Mark(mark.getOffset(), mark.getAttributes()));
221                 }
222
223                 if (marks.size() > 0) {
224                     populatedArea = new int [] {
225                         marks.get(0).getOffset(),
226                         marks.get(marks.size() - 1).getOffset()
227                     };
228                 }
229             }
230             
231             if (clearedArea != null) {
232                 changeStart = clearedArea[0];
233                 changeEnd = clearedArea[1];
234             }
235
236             if (populatedArea != null) {
237                 if (changeStart == Integer.MAX_VALUE || changeStart > populatedArea[0]) {
238                     changeStart = populatedArea[0];
239                 }
240                 if (changeEnd == Integer.MIN_VALUE || changeEnd < populatedArea[1]) {
241                     changeEnd = populatedArea[1];
242                 }
243             }
244             
245             if (changeStart < changeEnd) {
246                 version++;
247             }
248         }
249         
250         if (changeStart < changeEnd) {
251             fireHighlightsChange(changeStart, changeEnd);
252         }
253     }
254     
255     /**
256      * Removes highlights in a specific area of the document. All existing highlights
257      * that are positioned within the area specified by the <code>startOffset</code> and
258      * <code>endOffset</code> parameters are removed from this bag. The highlights
259      * that only partialy overlap with the area are treated according to the value
260      * of the <code>clip</code> parameter.
261      *
262      * <ul>
263      * <li>If <code>clip == true</code> : the overlapping highlights will remain
264      * in this sequence but will be clipped so that they do not overlap anymore
265      * <li>If <code>clip == false</code> : the overlapping highlights will be
266      * removed from this sequence
267      * </ul>
268      *
269      * @param startOffset The beginning of the area to clear.
270      * @param endOffset The end of the area to clear.
271      */

272     public void removeHighlights(int startOffset, int endOffset, boolean clip) {
273         int changeStart = Integer.MAX_VALUE;
274         int changeEnd = Integer.MIN_VALUE;
275
276         // Ignore empty areas
277
if (startOffset == endOffset) {
278             return;
279         } else {
280             assert startOffset < endOffset : "Start offset must be before the end offset. startOffset = " + startOffset + ", endOffset = " + endOffset; //NOI18N
281
}
282         
283         synchronized (marks) {
284             if (marks.isEmpty()) {
285                 return;
286             }
287             
288             int startIdx = indexBeforeOffset(startOffset);
289             int endIdx = indexBeforeOffset(endOffset, startIdx < 0 ? 0 : startIdx, marks.size() - 1);
290             
291 // System.out.println("removeHighlights(" + startOffset + ", " + endOffset + ", " + clip + ") : startIdx = " + startIdx + ", endIdx = " + endIdx);
292

293             if (clip) {
294                 if (startIdx == endIdx) {
295                     if (startIdx != -1 && marks.get(startIdx).getAttributes() != null) {
296                         AttributeSet JavaDoc original = marks.get(startIdx).getAttributes();
297                         
298                         if (marks.get(startIdx).getOffset() == startOffset) {
299                             marks.set(startIdx, new Mark(endOffset, original));
300                         } else {
301                             marks.add(startIdx + 1, new Mark(startOffset, null));
302                             marks.add(startIdx + 2, new Mark(endOffset, original));
303                         }
304                         
305                         changeStart = startOffset;
306                         changeEnd = endOffset;
307                     }
308                     
309                     // make sure nothing gets removed
310
startIdx = Integer.MAX_VALUE;
311                     endIdx = Integer.MIN_VALUE;
312                 } else {
313                     assert endIdx != -1 : "Invalid range: startIdx = " + startIdx + " endIdx = " + endIdx;
314
315                     if (marks.get(endIdx).getAttributes() != null) {
316                         marks.set(endIdx, new Mark(endOffset, marks.get(endIdx).getAttributes()));
317                         changeEnd = endOffset;
318                         endIdx--;
319                     }
320                     
321                     if (startIdx != -1 && marks.get(startIdx).getAttributes() != null) {
322                         startIdx++;
323                         if (startIdx <= endIdx) {
324                             marks.set(startIdx, new Mark(startOffset, null));
325                         } else {
326                             marks.add(startIdx, new Mark(startOffset, null));
327                         }
328                         changeStart = startOffset;
329                     }
330                     startIdx++;
331
332                 }
333             } else {
334                 if (startIdx == -1 || marks.get(startIdx).getAttributes() == null) {
335                     startIdx++;
336                 }
337                 
338                 if (endIdx != -1 && marks.get(endIdx).getAttributes() != null) {
339                     endIdx++;
340                 }
341             }
342             
343             if (startIdx <= endIdx) {
344                 if (changeStart == Integer.MAX_VALUE) {
345                     changeStart = marks.get(startIdx).getOffset();
346                 }
347                 if (changeEnd == Integer.MIN_VALUE) {
348                     changeEnd = marks.get(endIdx).getOffset();
349                 }
350                 marks.remove(startIdx, endIdx - startIdx + 1);
351             }
352             
353             if (changeStart < changeEnd) {
354                 version++;
355             }
356         }
357         
358         if (changeStart < changeEnd) {
359             fireHighlightsChange(changeStart, changeEnd);
360         }
361     }
362
363     /**
364      * Gets highlights from an area of a document. The <code>HighlightsSequence</code> is
365      * computed using all the highlights present in this bag between the
366      * <code>startOffset</code> and <code>endOffset</code>.
367      *
368      * @param startOffset The beginning of the area.
369      * @param endOffset The end of the area.
370      *
371      * @return The <code>HighlightsSequence</code> which iterates through the
372      * highlights in the given area of this bag.
373      */

374     public HighlightsSequence getHighlights(int startOffset, int endOffset) {
375         if (LOG.isLoggable(Level.FINE) && !(startOffset < endOffset)) {
376             LOG.fine("startOffset must be less than endOffset: startOffset = " + //NOI18N
377
startOffset + " endOffset = " + endOffset); //NOI18N
378
}
379         
380         synchronized (marks) {
381             return new Seq(version, startOffset, endOffset);
382         }
383     }
384
385     /**
386      * Removes all highlights previously added to this bag.
387      */

388     public void clear() {
389         int [] clearedArea;
390         
391         synchronized (marks) {
392             clearedArea = clearImpl();
393             if (clearedArea != null) {
394                 version++;
395             }
396         }
397         
398         if (clearedArea != null) {
399             fireHighlightsChange(clearedArea[0], clearedArea[1]);
400         }
401     }
402     
403     // ----------------------------------------------------------------------
404
// Package private API
405
// ----------------------------------------------------------------------
406

407     /* package */ OffsetGapList<Mark> getMarks() {
408         return marks;
409     }
410
411     /* package */ Document JavaDoc getDocument() {
412         return document;
413     }
414
415     // ----------------------------------------------------------------------
416
// Private implementation
417
// ----------------------------------------------------------------------
418

419     private int [] addHighlightImpl(int startOffset, int endOffset, AttributeSet JavaDoc attributes) {
420         if (startOffset == endOffset) {
421             return null;
422         } else {
423             assert startOffset < endOffset : "Start offset must be before the end offset. startOffset = " + startOffset + ", endOffset = " + endOffset; //NOI18N
424
assert attributes != null : "Highlight attributes must not be null."; //NOI18N
425
}
426
427         if (mergeHighlights) {
428             merge(startOffset, endOffset, attributes);
429         } else {
430             trim(startOffset, endOffset, attributes);
431         }
432
433         return new int [] { startOffset, endOffset };
434     }
435     
436     private void merge(int startOffset, int endOffset, AttributeSet JavaDoc attributes) {
437         AttributeSet JavaDoc lastKnownAttributes = null;
438         int startIdx = indexBeforeOffset(startOffset);
439         if (startIdx < 0) {
440             startIdx = 0;
441             marks.add(startIdx, new Mark(startOffset, attributes));
442         } else {
443             Mark mark = marks.get(startIdx);
444             AttributeSet JavaDoc newAttribs = AttributesUtilities.createComposite(attributes, mark.getAttributes());
445             lastKnownAttributes = mark.getAttributes();
446
447             if (mark.getOffset() == startOffset) {
448                 mark.setAttributes(newAttribs);
449             } else {
450                 startIdx++;
451                 marks.add(startIdx, new Mark(startOffset, newAttribs));
452             }
453         }
454
455         for(int idx = startIdx + 1; ; idx++) {
456             if (idx < marks.size()) {
457                 Mark mark = marks.get(idx);
458
459                 if (mark.getOffset() < endOffset) {
460                     lastKnownAttributes = mark.getAttributes();
461                     mark.setAttributes(AttributesUtilities.createComposite(attributes, lastKnownAttributes));
462                 } else {
463                     if (mark.getOffset() > endOffset) {
464                         marks.add(idx, new Mark(endOffset, lastKnownAttributes));
465                     }
466                     break;
467                 }
468             } else {
469                 marks.add(idx, new Mark(endOffset, lastKnownAttributes));
470                 break;
471             }
472         }
473     }
474
475     private void trim(int startOffset, int endOffset, AttributeSet JavaDoc attributes) {
476         int startIdx = indexBeforeOffset(startOffset);
477         int endIdx = indexBeforeOffset(endOffset, startIdx < 0 ? 0 : startIdx, marks.size() - 1);
478         
479 // System.out.println("trim(" + startOffset + ", " + endOffset + ") : startIdx = " + startIdx + ", endIdx = " + endIdx);
480

481         if (startIdx == endIdx) {
482             AttributeSet JavaDoc original = null;
483             if (startIdx != -1 && marks.get(startIdx).getAttributes() != null) {
484                 original = marks.get(startIdx).getAttributes();
485             }
486             
487             if (startIdx != -1 && marks.get(startIdx).getOffset() == startOffset) {
488                 marks.get(startIdx).setAttributes(attributes);
489             } else {
490                 startIdx++;
491                 marks.add(startIdx, new Mark(startOffset, attributes));
492             }
493             
494             startIdx++;
495             marks.add(startIdx, new Mark(endOffset, original));
496         } else {
497             assert endIdx != -1 : "Invalid range: startIdx = " + startIdx + " endIdx = " + endIdx; //NOI81N
498

499             marks.set(endIdx, new Mark(endOffset, marks.get(endIdx).getAttributes()));
500             endIdx--;
501
502             startIdx++;
503             if (startIdx <= endIdx) {
504                 marks.set(startIdx, new Mark(startOffset, attributes));
505             } else {
506                 marks.add(startIdx, new Mark(startOffset, attributes));
507             }
508             startIdx++;
509
510             if (startIdx <= endIdx) {
511                 marks.remove(startIdx, endIdx - startIdx + 1);
512             }
513         }
514     }
515     
516     private int [] addAllHighlightsImpl(HighlightsSequence sequence) {
517         int changeStart = Integer.MAX_VALUE;
518         int changeEnd = Integer.MIN_VALUE;
519
520         for ( ; sequence.moveNext(); ) {
521             addHighlightImpl(sequence.getStartOffset(), sequence.getEndOffset(), sequence.getAttributes());
522
523             if (changeStart == Integer.MAX_VALUE) {
524                 changeStart = sequence.getStartOffset();
525             }
526             changeEnd = sequence.getEndOffset();
527         }
528
529         if (changeStart != Integer.MAX_VALUE && changeEnd != Integer.MIN_VALUE) {
530             return new int [] { changeStart, changeEnd };
531         } else {
532             return null;
533         }
534     }
535     
536     private int [] clearImpl() {
537         if (!marks.isEmpty()) {
538             int changeStart = marks.get(0).getOffset();
539             int changeEnd = marks.get(marks.size() - 1).getOffset();
540
541             marks.clear();
542
543             return new int [] { changeStart, changeEnd };
544         } else {
545             return null;
546         }
547     }
548
549     private int indexBeforeOffset(int offset, int low, int high) {
550         int idx = marks.findElementIndex(offset, low, high);
551         if (idx < 0) {
552             idx = -idx - 1; // the insertion point: <0, size()>
553
return idx - 1;
554         } else {
555             return idx;
556         }
557     }
558     
559     private int indexBeforeOffset(int offset) {
560         return indexBeforeOffset(offset, 0, marks.size() - 1);
561     }
562     
563     /* package */ static final class Mark extends OffsetGapList.Offset {
564         private AttributeSet JavaDoc attribs;
565         
566         public Mark(int offset, AttributeSet JavaDoc attribs) {
567             super(offset);
568             this.attribs = attribs;
569         }
570         
571         public AttributeSet JavaDoc getAttributes() {
572             return attribs;
573         }
574         
575         public void setAttributes(AttributeSet JavaDoc attribs) {
576             this.attribs = attribs;
577         }
578     } // End of Mark class
579

580     private final class Seq implements HighlightsSequence {
581
582         private long version;
583         private int startOffset;
584         private int endOffset;
585
586         private int idx = -1;
587
588         public Seq(long version, int startOffset, int endOffset) {
589             this.version = version;
590             this.startOffset = startOffset;
591             this.endOffset = endOffset;
592         }
593
594         public boolean moveNext() {
595             synchronized (OffsetsBag.this.marks) {
596                 checkVersion();
597                 
598                 if (idx == -1) {
599                     idx = indexBeforeOffset(startOffset);
600                     if (idx == -1 && marks.size() > 0) {
601                         idx = 0;
602                     }
603                 } else {
604                     idx++;
605                 }
606
607                 while (isIndexValid(idx)) {
608                     if (marks.get(idx).getAttributes() != null) {
609                         return true;
610                     }
611
612                     // Skip any empty areas
613
idx++;
614                 }
615
616                 return false;
617             }
618         }
619
620         public int getStartOffset() {
621             synchronized (OffsetsBag.this.marks) {
622                 assert idx != -1 : "Sequence not initialized, call moveNext() first."; //NOI18N
623
checkVersion();
624                 
625                 if (isIndexValid(idx)) {
626                     return Math.max(marks.get(idx).getOffset(), startOffset);
627                 } else {
628                     throw new NoSuchElementException JavaDoc();
629                 }
630             }
631         }
632
633         public int getEndOffset() {
634             synchronized (OffsetsBag.this.marks) {
635                 assert idx != -1 : "Sequence not initialized, call moveNext() first."; //NOI18N
636
checkVersion();
637                 
638                 if (isIndexValid(idx)) {
639                     return Math.min(marks.get(idx + 1).getOffset(), endOffset);
640                 } else {
641                     throw new NoSuchElementException JavaDoc();
642                 }
643             }
644         }
645
646         public AttributeSet JavaDoc getAttributes() {
647             synchronized (OffsetsBag.this.marks) {
648                 assert idx != -1 : "Sequence not initialized, call moveNext() first."; //NOI18N
649
checkVersion();
650                 
651                 if (isIndexValid(idx)) {
652                     return marks.get(idx).getAttributes();
653                 } else {
654                     throw new NoSuchElementException JavaDoc();
655                 }
656             }
657         }
658         
659         private boolean isIndexValid(int idx) {
660             return idx >= 0 &&
661                     idx + 1 < marks.size() &&
662                     marks.get(idx).getOffset() < endOffset &&
663                     marks.get(idx + 1).getOffset() > startOffset;
664         }
665         
666         private OffsetsBag getBag() {
667             return OffsetsBag.this;
668         }
669         
670         private void checkVersion() {
671             if (OffsetsBag.this.version != version) {
672                 throw new ConcurrentModificationException JavaDoc();
673             }
674         }
675     } // End of Seq class
676

677     private static final class DocL extends WeakReference JavaDoc<OffsetsBag> implements DocumentListener JavaDoc, Runnable JavaDoc {
678         
679         private Document JavaDoc document;
680         
681         public DocL(OffsetsBag bag) {
682             super(bag, Utilities.activeReferenceQueue());
683             this.document = bag.getDocument();
684         }
685         
686         public void insertUpdate(DocumentEvent JavaDoc e) {
687             OffsetsBag bag = get();
688             if (bag != null) {
689                 synchronized (bag.marks) {
690                     bag.marks.defaultInsertUpdate(e.getOffset(), e.getLength());
691                 }
692             } else {
693                 run();
694             }
695         }
696
697         public void removeUpdate(DocumentEvent JavaDoc e) {
698             OffsetsBag bag = get();
699             if (bag != null) {
700                 synchronized (bag.marks) {
701                     bag.marks.defaultRemoveUpdate(e.getOffset(), e.getLength());
702                 }
703             } else {
704                 run();
705             }
706         }
707
708         public void changedUpdate(DocumentEvent JavaDoc e) {
709             // not interested
710
}
711
712         public void run() {
713             Document JavaDoc d = document;
714             if (d != null) {
715                 d.removeDocumentListener(this);
716                 document = null;
717             }
718         }
719     } // End of DocL class
720
}
721
Popular Tags