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.spi.editor.highlighting.support;
043:
044: import java.lang.ref.WeakReference;
045: import java.util.logging.Level;
046: import java.util.logging.Logger;
047: import javax.swing.event.DocumentEvent;
048: import javax.swing.event.DocumentListener;
049: import javax.swing.text.AttributeSet;
050: import javax.swing.text.Document;
051: import org.netbeans.api.editor.settings.AttributesUtilities;
052: import org.netbeans.modules.editor.lib2.highlighting.OffsetGapList;
053: import org.netbeans.spi.editor.highlighting.*;
054: import org.openide.util.Utilities;
055:
056: /**
057: * A bag of highlighted areas specified by their offsets in a document.
058: *
059: * <p>The highlighted areas (highlights) are determined by their starting and ending
060: * offsets in a document and the set of attributes that should be used for rendering
061: * that area. The highlights can be arbitrarily added to and remove from this bag.
062: *
063: * <p>The <code>OffsetsBag</code> is designed to work over a single
064: * document, which is passed in to the constructor. All offsets
065: * accepted by various methods in this class must refer to positions within
066: * this document. Therefore any offsets passed in to the methods in this class
067: * have to be equal to or greater than zero and less than or equal to the document
068: * size.
069: *
070: * <p>The <code>OffsetsBag</code> can operate in two modes depending on a
071: * value passed in its construtor. The mode determines how the bag will treat
072: * newly added highlights that overlap with existing highlights in the bag.
073: *
074: * <p><b>Trimming mode</b>:
075: * In the trimming mode the bag will <i>trim</i> any existing highlights that
076: * would overlap with a newly added highlight. In this mode the newly
077: * added highlights always replace the existing highlights if they overlap.
078: *
079: * <p><b>Merging mode</b>:
080: * In the merging mode the bag will <i>merge</i> attributes of the overlapping highlights.
081: * The area where the new highlight overlaps with some existing highlights will
082: * then constitute a new highlight, which attributes will be a composition of
083: * the attributes of both the new and existing highlight. Should there be attributes
084: * with the same name the attribute values from the newly added highlight will take
085: * precedence.
086: *
087: * @author Vita Stejskal
088: */
089: public final class OffsetsBag extends AbstractHighlightsContainer {
090:
091: private static final Logger LOG = Logger.getLogger(OffsetsBag.class
092: .getName());
093:
094: private Document document;
095: private OffsetGapList<Mark> marks;
096: private boolean mergeHighlights;
097: private long version = 0;
098: private DocL docListener;
099:
100: /**
101: * Creates a new instance of <code>OffsetsBag</code>, which trims highlights
102: * as they are added. It calls the {@link #OffsetsBag(Document, boolean)} constructor
103: * passing <code>false</code> as a parameter.
104: *
105: * @param document The document that should be highlighted.
106: */
107: public OffsetsBag(Document document) {
108: this (document, false);
109: }
110:
111: /**
112: * Creates a new instance of <code>OffsetsBag</code>.
113: *
114: * @param document The document that should be highlighted.
115: * @param mergeHighlights Determines whether highlights should be merged
116: * or trimmed.
117: */
118: public OffsetsBag(Document document, boolean mergeHighlights) {
119: this .document = document;
120: this .mergeHighlights = mergeHighlights;
121: this .marks = new OffsetGapList<Mark>(true); // do not move 0 offset, #102955
122: this .docListener = new DocL(this );
123: this .document.addDocumentListener(docListener);
124: }
125:
126: /**
127: * Discards this <code>OffsetsBag</code>. This method should be called when
128: * a client stops using the bag. After calling this method no other methods
129: * should be called. The bag is effectively empty and it is not possible to
130: * modify it.
131: */
132: public void discard() {
133: synchronized (marks) {
134: if (document != null) {
135: document.removeDocumentListener(docListener);
136:
137: marks.clear();
138: version++;
139: docListener = null;
140: document = null;
141: }
142: }
143: }
144:
145: /**
146: * Adds a highlight to this bag. The highlight is specified by its staring
147: * and ending offset and by its attributes. Adding a highlight that overlaps
148: * with one or more existing highlights can have a different result depending
149: * on the value of the <code>mergingHighlights</code> parameter used for
150: * constructing this bag.
151: *
152: * @param startOffset The beginning of the highlighted area.
153: * @param endOffset The end of the highlighted area.
154: * @param attributes The attributes to use for highlighting.
155: */
156: public void addHighlight(int startOffset, int endOffset,
157: AttributeSet attributes) {
158: int[] offsets;
159:
160: synchronized (marks) {
161: offsets = addHighlightImpl(startOffset, endOffset,
162: attributes);
163: if (offsets != null) {
164: version++;
165: }
166: }
167:
168: if (offsets != null) {
169: fireHighlightsChange(offsets[0], offsets[1]);
170: }
171: }
172:
173: /**
174: * Adds all highlights from the bag passed in. This method is equivalent
175: * to calling <code>addHighlight</code> for all the highlights in the
176: * <code>bag</code> except that the changes are done atomically.
177: *
178: * @param bag The bag of highlights that will be atomically
179: * added to this bag.
180: */
181: public void addAllHighlights(HighlightsSequence bag) {
182: int[] offsets;
183:
184: synchronized (marks) {
185: offsets = addAllHighlightsImpl(bag);
186: if (offsets != null) {
187: version++;
188: }
189: }
190:
191: if (offsets != null) {
192: fireHighlightsChange(offsets[0], offsets[1]);
193: }
194: }
195:
196: /**
197: * Resets this bag to use the new set of highlights. This method drops
198: * all the existing highlights in this bag and adds all highlights from
199: * the sequence passed in as a parameter. The changes are made atomically.
200: * The sequence passed in has to be acting on the same <code>Document</code>
201: * as this bag.
202: *
203: * @param seq New highlights to add.
204: */
205: public void setHighlights(HighlightsSequence seq) {
206: if (seq instanceof Seq) {
207: setHighlights(((Seq) seq).getBag());
208: return;
209: }
210:
211: int changeStart = Integer.MAX_VALUE;
212: int changeEnd = Integer.MIN_VALUE;
213:
214: synchronized (marks) {
215: int[] clearedArea = clearImpl();
216: int[] populatedArea = addAllHighlightsImpl(seq);
217:
218: if (clearedArea != null) {
219: changeStart = clearedArea[0];
220: changeEnd = clearedArea[1];
221: }
222:
223: if (populatedArea != null) {
224: if (changeStart == Integer.MAX_VALUE
225: || changeStart > populatedArea[0]) {
226: changeStart = populatedArea[0];
227: }
228: if (changeEnd == Integer.MIN_VALUE
229: || changeEnd < populatedArea[1]) {
230: changeEnd = populatedArea[1];
231: }
232: }
233:
234: if (changeStart < changeEnd) {
235: version++;
236: }
237: }
238:
239: if (changeStart < changeEnd) {
240: fireHighlightsChange(changeStart, changeEnd);
241: }
242: }
243:
244: /**
245: * Resets this bag to use the new set of highlights. This method drops
246: * all the existing highlights in this bag and adds all highlights from
247: * the bag passed in as a parameter. The changes are made atomically. Both
248: * bags have to be acting on the same <code>Document</code>.
249: *
250: * @param bag New highlights to add.
251: */
252: public void setHighlights(OffsetsBag bag) {
253: int changeStart = Integer.MAX_VALUE;
254: int changeEnd = Integer.MIN_VALUE;
255:
256: synchronized (marks) {
257: assert document != null : "Can't modify discarded bag."; //NOI18N
258:
259: int[] clearedArea = clearImpl();
260: int[] populatedArea = null;
261:
262: OffsetGapList<OffsetsBag.Mark> newMarks = bag.getMarks();
263:
264: synchronized (newMarks) {
265: for (OffsetsBag.Mark mark : newMarks) {
266: marks.add(marks.size(), new OffsetsBag.Mark(mark
267: .getOffset(), mark.getAttributes()));
268: }
269:
270: if (marks.size() > 0) {
271: populatedArea = new int[] {
272: marks.get(0).getOffset(),
273: marks.get(marks.size() - 1).getOffset() };
274: }
275: }
276:
277: if (clearedArea != null) {
278: changeStart = clearedArea[0];
279: changeEnd = clearedArea[1];
280: }
281:
282: if (populatedArea != null) {
283: if (changeStart == Integer.MAX_VALUE
284: || changeStart > populatedArea[0]) {
285: changeStart = populatedArea[0];
286: }
287: if (changeEnd == Integer.MIN_VALUE
288: || changeEnd < populatedArea[1]) {
289: changeEnd = populatedArea[1];
290: }
291: }
292:
293: if (changeStart < changeEnd) {
294: version++;
295: }
296: }
297:
298: if (changeStart < changeEnd) {
299: fireHighlightsChange(changeStart, changeEnd);
300: }
301: }
302:
303: /**
304: * Removes highlights in a specific area of the document. All existing highlights
305: * that are positioned within the area specified by the <code>startOffset</code> and
306: * <code>endOffset</code> parameters are removed from this bag. The highlights
307: * that only partialy overlap with the area are treated according to the value
308: * of the <code>clip</code> parameter.
309: *
310: * <ul>
311: * <li>If <code>clip == true</code> : the overlapping highlights will remain
312: * in this sequence but will be clipped so that they do not overlap anymore
313: * <li>If <code>clip == false</code> : the overlapping highlights will be
314: * removed from this sequence
315: * </ul>
316: *
317: * @param startOffset The beginning of the area to clear.
318: * @param endOffset The end of the area to clear.
319: * @param clip Whether to clip the partially overlapping highlights.
320: */
321: public void removeHighlights(int startOffset, int endOffset,
322: boolean clip) {
323: int changeStart = Integer.MAX_VALUE;
324: int changeEnd = Integer.MIN_VALUE;
325:
326: // Ignore empty areas when clipping
327: if (startOffset == endOffset && clip) {
328: return;
329: } else {
330: assert startOffset <= endOffset : "Start offset must be less than or equal to the end offset. startOffset = "
331: + startOffset + ", endOffset = " + endOffset; //NOI18N
332: }
333:
334: synchronized (marks) {
335: assert document != null : "Can't modify discarded bag."; //NOI18N
336:
337: if (marks.isEmpty()) {
338: return;
339: }
340:
341: int startIdx = indexBeforeOffset(startOffset);
342: int endIdx = indexBeforeOffset(endOffset, startIdx < 0 ? 0
343: : startIdx, marks.size() - 1);
344:
345: // System.out.println("removeHighlights(" + startOffset + ", " + endOffset + ", " + clip + ") : startIdx = " + startIdx + ", endIdx = " + endIdx);
346:
347: if (clip) {
348: if (startIdx == endIdx) {
349: if (startIdx != -1
350: && marks.get(startIdx).getAttributes() != null) {
351: AttributeSet original = marks.get(startIdx)
352: .getAttributes();
353:
354: if (marks.get(startIdx).getOffset() == startOffset) {
355: marks.set(startIdx, new Mark(endOffset,
356: original));
357: } else {
358: marks.add(startIdx + 1, new Mark(
359: startOffset, null));
360: marks.add(startIdx + 2, new Mark(endOffset,
361: original));
362: }
363:
364: changeStart = startOffset;
365: changeEnd = endOffset;
366: }
367:
368: // make sure nothing gets removed
369: startIdx = Integer.MAX_VALUE;
370: endIdx = Integer.MIN_VALUE;
371: } else {
372: assert endIdx != -1 : "Invalid range: startIdx = "
373: + startIdx + " endIdx = " + endIdx;
374:
375: if (marks.get(endIdx).getAttributes() != null) {
376: marks.set(endIdx, new Mark(endOffset, marks
377: .get(endIdx).getAttributes()));
378: changeEnd = endOffset;
379: endIdx--;
380: }
381:
382: if (startIdx != -1
383: && marks.get(startIdx).getAttributes() != null) {
384: startIdx++;
385: if (startIdx <= endIdx) {
386: marks.set(startIdx, new Mark(startOffset,
387: null));
388: } else {
389: marks.add(startIdx, new Mark(startOffset,
390: null));
391: }
392: changeStart = startOffset;
393: }
394: startIdx++;
395:
396: }
397: } else {
398: if (startIdx == -1
399: || marks.get(startIdx).getAttributes() == null) {
400: startIdx++;
401: }
402:
403: if (endIdx != -1
404: && marks.get(endIdx).getAttributes() != null) {
405: endIdx++;
406: }
407: }
408:
409: if (startIdx <= endIdx) {
410: if (changeStart == Integer.MAX_VALUE) {
411: changeStart = marks.get(startIdx).getOffset();
412: }
413: if (changeEnd == Integer.MIN_VALUE) {
414: if (endIdx >= marks.size()) { // Logging for #117403
415: if (LOG.isLoggable(Level.INFO)) {
416: String logMsg = "Too high endIdx="
417: + endIdx
418: + // NOI18N
419: ", marks.size()="
420: + marks.size()
421: + // NOI18N
422: ", startIdx="
423: + startIdx
424: + // NOI18N
425: ", startOffset=" + startOffset
426: + ", endOffset="
427: + endOffset
428: + // NOI18N
429: ", changeStart="
430: + changeStart
431: + // NOI18N
432: ", document.getLength()="
433: + document.getLength()
434: + // NOI18N
435: ", lastMark="
436: + marks.get(marks.size() - 1); // NOI18N
437: LOG
438: .log(Level.INFO, logMsg,
439: new Exception());
440: }
441: endIdx = marks.size() - 1; // Fix the index so that exc. does not occur
442: }
443: changeEnd = marks.get(endIdx).getOffset();
444: }
445: marks.remove(startIdx, endIdx - startIdx + 1);
446: }
447:
448: if (changeStart <= changeEnd) {
449: version++;
450: }
451: }
452:
453: if (changeStart <= changeEnd) {
454: fireHighlightsChange(changeStart, changeEnd);
455: }
456: }
457:
458: /**
459: * Gets highlights from an area of a document. The <code>HighlightsSequence</code> is
460: * computed using all the highlights present in this bag between the
461: * <code>startOffset</code> and <code>endOffset</code>.
462: *
463: * @param startOffset The beginning of the area.
464: * @param endOffset The end of the area.
465: *
466: * @return The <code>HighlightsSequence</code> which iterates through the
467: * highlights in the given area of this bag.
468: */
469: public HighlightsSequence getHighlights(int startOffset,
470: int endOffset) {
471: if (LOG.isLoggable(Level.FINE) && !(startOffset < endOffset)) {
472: LOG
473: .fine("startOffset must be less than endOffset: startOffset = "
474: + //NOI18N
475: startOffset + " endOffset = " + endOffset); //NOI18N
476: }
477:
478: synchronized (marks) {
479: if (document != null) {
480: return new Seq(version, startOffset, endOffset);
481: } else {
482: return HighlightsSequence.EMPTY;
483: }
484: }
485: }
486:
487: /**
488: * Removes all highlights previously added to this bag.
489: */
490: public void clear() {
491: int[] clearedArea;
492:
493: synchronized (marks) {
494: assert document != null : "Can't modify discarded bag."; //NOI18N
495:
496: clearedArea = clearImpl();
497: if (clearedArea != null) {
498: version++;
499: }
500: }
501:
502: if (clearedArea != null) {
503: fireHighlightsChange(clearedArea[0], clearedArea[1]);
504: }
505: }
506:
507: // ----------------------------------------------------------------------
508: // Package private API
509: // ----------------------------------------------------------------------
510:
511: /* package */OffsetGapList<Mark> getMarks() {
512: return marks;
513: }
514:
515: /* package */Document getDocument() {
516: return document;
517: }
518:
519: // ----------------------------------------------------------------------
520: // Private implementation
521: // ----------------------------------------------------------------------
522:
523: private int[] addHighlightImpl(int startOffset, int endOffset,
524: AttributeSet attributes) {
525: if (startOffset == endOffset) {
526: return null;
527: } else {
528: assert document != null : "Can't modify discarded bag."; //NOI18N
529: assert startOffset < endOffset : "Start offset must be before the end offset. startOffset = "
530: + startOffset + ", endOffset = " + endOffset; //NOI18N
531: assert attributes != null : "Highlight attributes must not be null."; //NOI18N
532: }
533:
534: if (mergeHighlights) {
535: merge(startOffset, endOffset, attributes);
536: } else {
537: trim(startOffset, endOffset, attributes);
538: }
539:
540: return new int[] { startOffset, endOffset };
541: }
542:
543: private void merge(int startOffset, int endOffset,
544: AttributeSet attributes) {
545: AttributeSet lastKnownAttributes = null;
546: int startIdx = indexBeforeOffset(startOffset);
547: if (startIdx < 0) {
548: startIdx = 0;
549: marks.add(startIdx, new Mark(startOffset, attributes));
550: } else {
551: Mark mark = marks.get(startIdx);
552: AttributeSet markAttribs = mark.getAttributes();
553: AttributeSet newAttribs = markAttribs == null ? attributes
554: : AttributesUtilities.createComposite(attributes,
555: markAttribs);
556: lastKnownAttributes = mark.getAttributes();
557:
558: if (mark.getOffset() == startOffset) {
559: mark.setAttributes(newAttribs);
560: } else {
561: startIdx++;
562: marks.add(startIdx, new Mark(startOffset, newAttribs));
563: }
564: }
565:
566: for (int idx = startIdx + 1;; idx++) {
567: if (idx < marks.size()) {
568: Mark mark = marks.get(idx);
569:
570: if (mark.getOffset() < endOffset) {
571: lastKnownAttributes = mark.getAttributes();
572: mark
573: .setAttributes(lastKnownAttributes == null ? attributes
574: : AttributesUtilities
575: .createComposite(
576: attributes,
577: lastKnownAttributes));
578: } else {
579: if (mark.getOffset() > endOffset) {
580: marks.add(idx, new Mark(endOffset,
581: lastKnownAttributes));
582: }
583: break;
584: }
585: } else {
586: marks
587: .add(idx, new Mark(endOffset,
588: lastKnownAttributes));
589: break;
590: }
591: }
592: }
593:
594: private void trim(int startOffset, int endOffset,
595: AttributeSet attributes) {
596: int startIdx = indexBeforeOffset(startOffset);
597: int endIdx = indexBeforeOffset(endOffset, startIdx < 0 ? 0
598: : startIdx, marks.size() - 1);
599:
600: // System.out.println("trim(" + startOffset + ", " + endOffset + ") : startIdx = " + startIdx + ", endIdx = " + endIdx);
601:
602: if (startIdx == endIdx) {
603: AttributeSet original = null;
604: if (startIdx != -1
605: && marks.get(startIdx).getAttributes() != null) {
606: original = marks.get(startIdx).getAttributes();
607: }
608:
609: if (startIdx != -1
610: && marks.get(startIdx).getOffset() == startOffset) {
611: marks.get(startIdx).setAttributes(attributes);
612: } else {
613: startIdx++;
614: marks.add(startIdx, new Mark(startOffset, attributes));
615: }
616:
617: startIdx++;
618: marks.add(startIdx, new Mark(endOffset, original));
619: } else {
620: assert endIdx != -1 : "Invalid range: startIdx = "
621: + startIdx + " endIdx = " + endIdx; //NOI81N
622:
623: marks.set(endIdx, new Mark(endOffset, marks.get(endIdx)
624: .getAttributes()));
625: endIdx--;
626:
627: startIdx++;
628: if (startIdx <= endIdx) {
629: marks.set(startIdx, new Mark(startOffset, attributes));
630: } else {
631: marks.add(startIdx, new Mark(startOffset, attributes));
632: }
633: startIdx++;
634:
635: if (startIdx <= endIdx) {
636: marks.remove(startIdx, endIdx - startIdx + 1);
637: }
638: }
639: }
640:
641: private int[] addAllHighlightsImpl(HighlightsSequence sequence) {
642: int changeStart = Integer.MAX_VALUE;
643: int changeEnd = Integer.MIN_VALUE;
644:
645: for (; sequence.moveNext();) {
646: addHighlightImpl(sequence.getStartOffset(), sequence
647: .getEndOffset(), sequence.getAttributes());
648:
649: if (changeStart == Integer.MAX_VALUE) {
650: changeStart = sequence.getStartOffset();
651: }
652: changeEnd = sequence.getEndOffset();
653: }
654:
655: if (changeStart != Integer.MAX_VALUE
656: && changeEnd != Integer.MIN_VALUE) {
657: return new int[] { changeStart, changeEnd };
658: } else {
659: return null;
660: }
661: }
662:
663: private int[] clearImpl() {
664: if (!marks.isEmpty()) {
665: int changeStart = marks.get(0).getOffset();
666: int changeEnd = marks.get(marks.size() - 1).getOffset();
667:
668: marks.clear();
669:
670: return new int[] { changeStart, changeEnd };
671: } else {
672: return null;
673: }
674: }
675:
676: private int indexBeforeOffset(int offset, int low, int high) {
677: int idx = marks.findElementIndex(offset, low, high);
678: if (idx < 0) {
679: idx = -idx - 1; // the insertion point: <0, size()>
680: return idx - 1;
681: } else {
682: return idx;
683: }
684: }
685:
686: private int indexBeforeOffset(int offset) {
687: return indexBeforeOffset(offset, 0, marks.size() - 1);
688: }
689:
690: /* package */static final class Mark extends OffsetGapList.Offset {
691: private AttributeSet attribs;
692:
693: public Mark(int offset, AttributeSet attribs) {
694: super (offset);
695: this .attribs = attribs;
696: }
697:
698: public AttributeSet getAttributes() {
699: return attribs;
700: }
701:
702: public void setAttributes(AttributeSet attribs) {
703: this .attribs = attribs;
704: }
705:
706: @Override
707: public String toString() {
708: return "offset=" + getOffset() + ", attribs=" + attribs; // NOI81N
709: }
710:
711: } // End of Mark class
712:
713: private final class Seq implements HighlightsSequence {
714:
715: private long version;
716: private int startOffset;
717: private int endOffset;
718:
719: private int highlightStart;
720: private int highlightEnd;
721: private AttributeSet highlightAttributes;
722:
723: private int idx = -1;
724:
725: public Seq(long version, int startOffset, int endOffset) {
726: this .version = version;
727: this .startOffset = startOffset;
728: this .endOffset = endOffset;
729: }
730:
731: public boolean moveNext() {
732: synchronized (OffsetsBag.this .marks) {
733: if (checkVersion()) {
734: if (idx == -1) {
735: idx = indexBeforeOffset(startOffset);
736: if (idx == -1 && marks.size() > 0) {
737: idx = 0;
738: }
739: } else {
740: idx++;
741: }
742:
743: while (isIndexValid(idx)) {
744: if (marks.get(idx).getAttributes() != null) {
745: highlightStart = Math.max(marks.get(idx)
746: .getOffset(), startOffset);
747: highlightEnd = Math.min(marks.get(idx + 1)
748: .getOffset(), endOffset);
749: highlightAttributes = marks.get(idx)
750: .getAttributes();
751: return true;
752: }
753:
754: // Skip any empty areas
755: idx++;
756: }
757: }
758:
759: return false;
760: }
761: }
762:
763: public int getStartOffset() {
764: synchronized (OffsetsBag.this .marks) {
765: assert idx != -1 : "Sequence not initialized, call moveNext() first."; //NOI18N
766: return highlightStart;
767: }
768: }
769:
770: public int getEndOffset() {
771: synchronized (OffsetsBag.this .marks) {
772: assert idx != -1 : "Sequence not initialized, call moveNext() first."; //NOI18N
773: return highlightEnd;
774: }
775: }
776:
777: public AttributeSet getAttributes() {
778: synchronized (OffsetsBag.this .marks) {
779: assert idx != -1 : "Sequence not initialized, call moveNext() first."; //NOI18N
780: return highlightAttributes;
781: }
782: }
783:
784: private boolean isIndexValid(int idx) {
785: return idx >= 0 && idx + 1 < marks.size()
786: && marks.get(idx).getOffset() < endOffset
787: && marks.get(idx + 1).getOffset() > startOffset;
788: }
789:
790: private OffsetsBag getBag() {
791: return OffsetsBag.this ;
792: }
793:
794: private boolean checkVersion() {
795: return OffsetsBag.this .version == version;
796: }
797: } // End of Seq class
798:
799: private static final class DocL extends WeakReference<OffsetsBag>
800: implements DocumentListener, Runnable {
801:
802: private Document document;
803:
804: public DocL(OffsetsBag bag) {
805: super (bag, Utilities.activeReferenceQueue());
806: this .document = bag.getDocument();
807: }
808:
809: public void insertUpdate(DocumentEvent e) {
810: OffsetsBag bag = get();
811: if (bag != null) {
812: synchronized (bag.marks) {
813: if (LOG.isLoggable(Level.FINE)) {
814: LOG.fine("OffsetsBag@"
815: + Integer.toHexString(System
816: .identityHashCode(this ))
817: + //NOI18N
818: " insertUpdate: doc="
819: + Integer.toHexString(System
820: .identityHashCode(document))
821: + //NOI18N
822: ", offset=" + e.getOffset()
823: + ", insertLength=" + e.getLength() + //NOI18N
824: ", docLength=" + document.getLength()); //NOI18N
825: }
826: bag.marks.defaultInsertUpdate(e.getOffset(), e
827: .getLength());
828: }
829: } else {
830: run();
831: }
832: }
833:
834: public void removeUpdate(DocumentEvent e) {
835: OffsetsBag bag = get();
836: if (bag != null) {
837: synchronized (bag.marks) {
838: if (LOG.isLoggable(Level.FINE)) {
839: LOG.fine("OffsetsBag@"
840: + Integer.toHexString(System
841: .identityHashCode(this ))
842: + //NOI18N
843: " removeUpdate: doc="
844: + Integer.toHexString(System
845: .identityHashCode(document))
846: + //NOI18N
847: ", offset=" + e.getOffset()
848: + ", removedLength=" + e.getLength() + //NOI18N
849: ", docLength=" + document.getLength()); //NOI18N
850: }
851: bag.marks.defaultRemoveUpdate(e.getOffset(), e
852: .getLength());
853: }
854: } else {
855: run();
856: }
857: }
858:
859: public void changedUpdate(DocumentEvent e) {
860: // not interested
861: }
862:
863: public void run() {
864: Document d = document;
865: if (d != null) {
866: d.removeDocumentListener(this );
867: document = null;
868: }
869: }
870: } // End of DocL class
871: }
|