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