1 19 20 package org.netbeans.spi.editor.highlighting.support; 21 22 import java.lang.ref.WeakReference ; 23 import java.util.ConcurrentModificationException ; 24 import java.util.NoSuchElementException ; 25 import java.util.logging.Level ; 26 import java.util.logging.Logger ; 27 import javax.swing.event.DocumentEvent ; 28 import javax.swing.event.DocumentListener ; 29 import javax.swing.text.AttributeSet ; 30 import javax.swing.text.Document ; 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 69 public final class OffsetsBag extends AbstractHighlightsContainer { 70 71 private static final Logger LOG = Logger.getLogger(OffsetsBag.class.getName()); 72 73 private Document document; 74 private OffsetGapList<Mark> marks; 75 private boolean mergeHighlights; 76 private long version = 0; 77 private DocL docListener; 78 79 86 public OffsetsBag(Document document) { 87 this(document, false); 88 } 89 90 97 public OffsetsBag(Document 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 116 public void addHighlight(int startOffset, int endOffset, AttributeSet 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 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 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 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 272 public void removeHighlights(int startOffset, int endOffset, boolean clip) { 273 int changeStart = Integer.MAX_VALUE; 274 int changeEnd = Integer.MIN_VALUE; 275 276 if (startOffset == endOffset) { 278 return; 279 } else { 280 assert startOffset < endOffset : "Start offset must be before the end offset. startOffset = " + startOffset + ", endOffset = " + endOffset; } 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 293 if (clip) { 294 if (startIdx == endIdx) { 295 if (startIdx != -1 && marks.get(startIdx).getAttributes() != null) { 296 AttributeSet 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 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 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 = " + startOffset + " endOffset = " + endOffset); } 379 380 synchronized (marks) { 381 return new Seq(version, startOffset, endOffset); 382 } 383 } 384 385 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 407 OffsetGapList<Mark> getMarks() { 408 return marks; 409 } 410 411 Document getDocument() { 412 return document; 413 } 414 415 419 private int [] addHighlightImpl(int startOffset, int endOffset, AttributeSet 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; assert attributes != null : "Highlight attributes must not be null."; } 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 attributes) { 437 AttributeSet 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 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 attributes) { 476 int startIdx = indexBeforeOffset(startOffset); 477 int endIdx = indexBeforeOffset(endOffset, startIdx < 0 ? 0 : startIdx, marks.size() - 1); 478 479 481 if (startIdx == endIdx) { 482 AttributeSet 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; 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; 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 static final class Mark extends OffsetGapList.Offset { 564 private AttributeSet attribs; 565 566 public Mark(int offset, AttributeSet attribs) { 567 super(offset); 568 this.attribs = attribs; 569 } 570 571 public AttributeSet getAttributes() { 572 return attribs; 573 } 574 575 public void setAttributes(AttributeSet attribs) { 576 this.attribs = attribs; 577 } 578 } 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 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."; checkVersion(); 624 625 if (isIndexValid(idx)) { 626 return Math.max(marks.get(idx).getOffset(), startOffset); 627 } else { 628 throw new NoSuchElementException (); 629 } 630 } 631 } 632 633 public int getEndOffset() { 634 synchronized (OffsetsBag.this.marks) { 635 assert idx != -1 : "Sequence not initialized, call moveNext() first."; checkVersion(); 637 638 if (isIndexValid(idx)) { 639 return Math.min(marks.get(idx + 1).getOffset(), endOffset); 640 } else { 641 throw new NoSuchElementException (); 642 } 643 } 644 } 645 646 public AttributeSet getAttributes() { 647 synchronized (OffsetsBag.this.marks) { 648 assert idx != -1 : "Sequence not initialized, call moveNext() first."; checkVersion(); 650 651 if (isIndexValid(idx)) { 652 return marks.get(idx).getAttributes(); 653 } else { 654 throw new NoSuchElementException (); 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 (); 673 } 674 } 675 } 677 private static final class DocL extends WeakReference <OffsetsBag> implements DocumentListener , Runnable { 678 679 private Document document; 680 681 public DocL(OffsetsBag bag) { 682 super(bag, Utilities.activeReferenceQueue()); 683 this.document = bag.getDocument(); 684 } 685 686 public void insertUpdate(DocumentEvent 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 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 e) { 709 } 711 712 public void run() { 713 Document d = document; 714 if (d != null) { 715 d.removeDocumentListener(this); 716 document = null; 717 } 718 } 719 } } 721 | Popular Tags |