1 19 20 package org.netbeans.spi.editor.highlighting.support; 21 22 import java.util.ConcurrentModificationException ; 23 import java.util.NoSuchElementException ; 24 import java.util.logging.Level ; 25 import java.util.logging.Logger ; 26 import javax.swing.text.AttributeSet ; 27 import javax.swing.text.Document ; 28 import javax.swing.text.Position ; 29 import org.netbeans.api.editor.settings.AttributesUtilities; 30 import org.netbeans.lib.editor.util.GapList; 31 import org.netbeans.spi.editor.highlighting.*; 32 33 67 public final class PositionsBag extends AbstractHighlightsContainer { 68 69 private static final Logger LOG = Logger.getLogger(PositionsBag.class.getName()); 70 71 private Document document; 72 private boolean mergeHighlights; 73 74 private GapList<Position > marks = new GapList<Position >(); 75 private GapList<AttributeSet > attributes = new GapList<AttributeSet >(); 76 private long version = 0; 77 78 85 public PositionsBag(Document document) { 86 this(document, false); 87 } 88 89 96 public PositionsBag(Document document, boolean mergeHighlights) { 97 this.document = document; 98 this.mergeHighlights = mergeHighlights; 99 } 100 101 112 public void addHighlight(Position startPosition, Position endPosition, AttributeSet attributes) { 113 int [] offsets; 114 115 synchronized (marks) { 116 offsets = addHighlightImpl(startPosition, endPosition, attributes); 117 if (offsets != null) { 118 version++; 119 } 120 } 121 122 if (offsets != null) { 123 fireHighlightsChange(offsets[0], offsets[1]); 124 } 125 } 126 127 134 public void addAllHighlights(PositionsBag bag) { 135 int [] offsets; 136 137 synchronized (marks) { 138 offsets = addAllHighlightsImpl(bag); 139 if (offsets != null) { 140 version++; 141 } 142 } 143 144 if (offsets != null) { 145 fireHighlightsChange(offsets[0], offsets[1]); 146 } 147 } 148 149 156 public void setHighlights(PositionsBag bag) { 157 int changeStart = Integer.MAX_VALUE; 158 int changeEnd = Integer.MIN_VALUE; 159 160 synchronized (marks) { 161 int [] clearedArea = clearImpl(); 162 int [] populatedArea = null; 163 164 GapList<Position > newMarks = bag.getMarks(); 165 GapList<AttributeSet > newAttributes = bag.getAttributes(); 166 167 synchronized (newMarks) { 168 for(Position mark : newMarks) { 169 marks.add(marks.size(), mark); 170 } 171 172 for(AttributeSet attrib : newAttributes) { 173 attributes.add(attributes.size(), attrib); 174 } 175 176 if (marks.size() > 0) { 177 populatedArea = new int [] { 178 marks.get(0).getOffset(), 179 marks.get(marks.size() - 1).getOffset() 180 }; 181 } 182 } 183 184 if (clearedArea != null) { 185 changeStart = clearedArea[0]; 186 changeEnd = clearedArea[1]; 187 } 188 189 if (populatedArea != null) { 190 if (changeStart == Integer.MAX_VALUE || changeStart > populatedArea[0]) { 191 changeStart = populatedArea[0]; 192 } 193 if (changeEnd == Integer.MIN_VALUE || changeEnd < populatedArea[1]) { 194 changeEnd = populatedArea[1]; 195 } 196 } 197 198 if (changeStart < changeEnd) { 199 version++; 200 } 201 } 202 203 if (changeStart < changeEnd) { 204 fireHighlightsChange(changeStart, changeEnd); 205 } 206 } 207 208 225 public void removeHighlights(Position startPosition, Position endPosition, boolean clip) { 226 if (!clip) { 227 removeHighlights(startPosition.getOffset(), endPosition.getOffset()); 228 return; 229 } 230 231 int changeStart = Integer.MAX_VALUE; 232 int changeEnd = Integer.MIN_VALUE; 233 234 if (startPosition.getOffset() == endPosition.getOffset()) { 236 return; 237 } else { 238 assert startPosition.getOffset() < endPosition.getOffset() : 239 "Start position must be before the end position"; } 241 242 synchronized (marks) { 243 if (marks.isEmpty()) { 244 return; 245 } 246 247 int startIdx = indexBeforeOffset(startPosition.getOffset()); 248 int endIdx = indexBeforeOffset(endPosition.getOffset(), startIdx < 0 ? 0 : startIdx, marks.size() - 1); 249 250 252 if (startIdx == endIdx) { 253 if (startIdx != -1 && attributes.get(startIdx) != null) { 254 AttributeSet original = attributes.get(startIdx); 255 256 if (marks.get(startIdx).getOffset() == startPosition.getOffset()) { 257 marks.set(startIdx, endPosition); 258 attributes.set(startIdx, original); 259 } else { 260 marks.add(startIdx + 1, startPosition); 261 attributes.add(startIdx + 1, null); 262 marks.add(startIdx + 2, endPosition); 263 attributes.add(startIdx + 2, original); 264 } 265 266 changeStart = startPosition.getOffset(); 267 changeEnd = endPosition.getOffset(); 268 } 269 } else { 270 assert endIdx != -1 : "Invalid range: startIdx = " + startIdx + " endIdx = " + endIdx; 271 272 if (attributes.get(endIdx) != null) { 273 marks.set(endIdx, endPosition); 274 changeEnd = endPosition.getOffset(); 275 endIdx--; 276 } 277 278 if (startIdx != -1 && attributes.get(startIdx) != null) { 279 startIdx++; 280 if (startIdx <= endIdx) { 281 marks.set(startIdx, startPosition); 282 attributes.set(startIdx, null); 283 } else { 284 marks.add(startIdx, startPosition); 285 attributes.add(startIdx, null); 286 } 287 changeStart = startPosition.getOffset(); 288 } 289 startIdx++; 290 291 if (startIdx <= endIdx) { 292 if (changeStart == Integer.MAX_VALUE) { 293 changeStart = marks.get(startIdx).getOffset(); 294 } 295 if (changeEnd == Integer.MIN_VALUE) { 296 changeEnd = marks.get(endIdx).getOffset(); 297 } 298 marks.remove(startIdx, endIdx - startIdx + 1); 299 attributes.remove(startIdx, endIdx - startIdx + 1); 300 } 301 } 302 303 if (changeStart < changeEnd) { 304 version++; 305 } 306 } 307 308 if (changeStart < changeEnd) { 309 fireHighlightsChange(changeStart, changeEnd); 310 } 311 } 312 313 322 public void removeHighlights(int startOffset, int endOffset) { 323 int changeStart = Integer.MAX_VALUE; 324 int changeEnd = Integer.MIN_VALUE; 325 326 if (startOffset == endOffset) { 328 return; 329 } else { 330 assert startOffset < endOffset : "Start position must be before the end position"; } 332 333 synchronized (marks) { 334 if (marks.isEmpty()) { 335 return; 336 } 337 338 int startIdx = indexBeforeOffset(startOffset); 339 int endIdx = indexBeforeOffset(endOffset, startIdx < 0 ? 0 : startIdx, marks.size() - 1); 340 341 343 if (startIdx == -1 || attributes.get(startIdx) == null) { 344 startIdx++; 345 } 346 347 if (endIdx != -1 && attributes.get(endIdx) != null) { 348 endIdx++; 349 } 350 351 if (startIdx < endIdx) { 352 if (changeStart == Integer.MAX_VALUE) { 353 changeStart = marks.get(startIdx).getOffset(); 354 } 355 if (changeEnd == Integer.MIN_VALUE) { 356 changeEnd = marks.get(endIdx).getOffset(); 357 } 358 marks.remove(startIdx, endIdx - startIdx + 1); 359 } 360 361 if (changeStart < changeEnd) { 362 version++; 363 } 364 } 365 366 if (changeStart < changeEnd) { 367 fireHighlightsChange(changeStart, changeEnd); 368 } 369 } 370 371 382 public HighlightsSequence getHighlights(int startOffset, int endOffset) { 383 if (LOG.isLoggable(Level.FINE) && !(startOffset < endOffset)) { 384 LOG.fine("startOffset must be less than endOffset: startOffset = " + startOffset + " endOffset = " + endOffset); } 387 388 synchronized (marks) { 389 return new Seq(version, startOffset, endOffset); 390 } 391 } 392 393 396 public void clear() { 397 int [] clearedArea; 398 399 synchronized (marks) { 400 clearedArea = clearImpl(); 401 if (clearedArea != null) { 402 version++; 403 } 404 } 405 406 if (clearedArea != null) { 407 fireHighlightsChange(clearedArea[0], clearedArea[1]); 408 } 409 } 410 411 415 GapList<Position > getMarks() { 416 return marks; 417 } 418 419 GapList<AttributeSet > getAttributes() { 420 return attributes; 421 } 422 423 427 private int [] addHighlightImpl(Position startPosition, Position endPosition, AttributeSet attributes) { 428 if (startPosition.getOffset() == endPosition.getOffset()) { 429 return null; 430 } else { 431 assert startPosition.getOffset() < endPosition.getOffset() : 432 "Start position must be before the end position."; assert attributes != null : "Highlight attributes must not be null."; } 435 436 if (mergeHighlights) { 437 merge(startPosition, endPosition, attributes); 438 } else { 439 trim(startPosition, endPosition, attributes); 440 } 441 442 return new int [] { startPosition.getOffset(), endPosition.getOffset() }; 443 } 444 445 private void merge(Position startPosition, Position endPosition, AttributeSet attrSet) { 446 AttributeSet lastKnownAttributes = null; 447 int startIdx = indexBeforeOffset(startPosition.getOffset()); 448 if (startIdx < 0) { 449 startIdx = 0; 450 marks.add(startIdx, startPosition); 451 attributes.add(startIdx, attrSet); 452 } else { 453 Position mark = marks.get(startIdx); 454 AttributeSet newAttribs = AttributesUtilities.createComposite(attrSet, attributes.get(startIdx)); 455 lastKnownAttributes = attributes.get(startIdx); 456 457 if (mark.getOffset() == startPosition.getOffset()) { 458 attributes.set(startIdx, newAttribs); 459 } else { 460 startIdx++; 461 marks.add(startIdx, startPosition); 462 attributes.add(startIdx, newAttribs); 463 } 464 } 465 466 for(int idx = startIdx + 1; ; idx++) { 467 if (idx < marks.size()) { 468 Position mark = marks.get(idx); 469 470 if (mark.getOffset() < endPosition.getOffset()) { 471 lastKnownAttributes = attributes.get(idx); 472 attributes.set(idx, AttributesUtilities.createComposite(attrSet, lastKnownAttributes)); 473 } else { 474 if (mark.getOffset() > endPosition.getOffset()) { 475 marks.add(idx, endPosition); 476 attributes.add(idx, lastKnownAttributes); 477 } 478 break; 479 } 480 } else { 481 marks.add(idx, endPosition); 482 attributes.add(idx, lastKnownAttributes); 483 break; 484 } 485 } 486 } 487 488 private void trim(Position startPosition, Position endPosition, AttributeSet attrSet) { 489 int startIdx = indexBeforeOffset(startPosition.getOffset()); 490 int endIdx = indexBeforeOffset(endPosition.getOffset(), startIdx < 0 ? 0 : startIdx, marks.size() - 1); 491 492 494 if (startIdx == endIdx) { 495 AttributeSet original = null; 496 if (startIdx != -1 && attributes.get(startIdx) != null) { 497 original = attributes.get(startIdx); 498 } 499 500 if (startIdx != -1 && marks.get(startIdx).getOffset() == startPosition.getOffset()) { 501 attributes.set(startIdx, attrSet); 502 } else { 503 startIdx++; 504 marks.add(startIdx, startPosition); 505 attributes.add(startIdx, attrSet); 506 } 507 508 startIdx++; 509 marks.add(startIdx, endPosition); 510 attributes.add(startIdx, original); 511 } else { 512 assert endIdx != -1 : "Invalid range: startIdx = " + startIdx + " endIdx = " + endIdx; 513 514 marks.set(endIdx, endPosition); 515 attributes.set(endIdx, attributes.get(endIdx)); 516 endIdx--; 517 518 startIdx++; 519 if (startIdx <= endIdx) { 520 marks.set(startIdx, startPosition); 521 attributes.set(startIdx, attrSet); 522 } else { 523 marks.add(startIdx, startPosition); 524 attributes.add(startIdx, attrSet); 525 } 526 startIdx++; 527 528 if (startIdx <= endIdx) { 529 marks.remove(startIdx, endIdx - startIdx + 1); 530 attributes.remove(startIdx, endIdx - startIdx + 1); 531 } 532 } 533 } 534 535 private int [] addAllHighlightsImpl(PositionsBag bag) { 536 int changeStart = Integer.MAX_VALUE; 537 int changeEnd = Integer.MIN_VALUE; 538 539 GapList<Position > newMarks = bag.getMarks(); 540 GapList<AttributeSet > newAttributes = bag.getAttributes(); 541 542 for (int i = 0 ; i + 1 < newMarks.size(); i++) { 543 Position mark1 = newMarks.get(i); 544 Position mark2 = newMarks.get(i + 1); 545 AttributeSet attrSet = newAttributes.get(i); 546 547 if (attrSet == null) { 548 continue; 550 } 551 552 addHighlightImpl(mark1, mark2, attrSet); 553 554 if (changeStart == Integer.MAX_VALUE) { 555 changeStart = mark1.getOffset(); 556 } 557 changeEnd = mark2.getOffset(); 558 } 559 560 if (changeStart != Integer.MAX_VALUE && changeEnd != Integer.MIN_VALUE) { 561 return new int [] { changeStart, changeEnd }; 562 } else { 563 return null; 564 } 565 } 566 567 private int [] clearImpl() { 568 if (!marks.isEmpty()) { 569 int changeStart = marks.get(0).getOffset(); 570 int changeEnd = marks.get(marks.size() - 1).getOffset(); 571 572 marks.clear(); 573 attributes.clear(); 574 575 return new int [] { changeStart, changeEnd }; 576 } else { 577 return null; 578 } 579 } 580 581 private int indexBeforeOffset(int offset, int low, int high) { 582 int idx = findElementIndex(offset, low, high); 583 if (idx < 0) { 584 idx = -idx - 1; return idx - 1; 586 } else { 587 return idx; 588 } 589 } 590 591 private int indexBeforeOffset(int offset) { 592 return indexBeforeOffset(offset, 0, marks.size() - 1); 593 } 594 595 private int findElementIndex(int offset, int lowIdx, int highIdx) { 596 if (lowIdx < 0 || highIdx > marks.size() - 1) { 597 throw new IndexOutOfBoundsException ("lowIdx = " + lowIdx + ", highIdx = " + highIdx + ", size = " + marks.size()); 598 } 599 600 int low = lowIdx; 601 int high = highIdx; 602 603 while (low <= high) { 604 int index = (low + high) >> 1; int elemOffset = marks.get(index).getOffset(); 606 607 if (elemOffset < offset) { 608 low = index + 1; 609 610 } else if (elemOffset > offset) { 611 high = index - 1; 612 613 } else { while (index > 0) { 615 index--; 616 if (marks.get(index).getOffset() < offset) { 617 index++; 618 break; 619 } 620 } 621 return index; 622 } 623 } 624 625 return -(low + 1); 626 } 627 628 private final class Seq implements HighlightsSequence { 629 630 private long version; 631 private int startOffset; 632 private int endOffset; 633 634 private int idx = -1; 635 636 public Seq(long version, int startOffset, int endOffset) { 637 this.version = version; 638 this.startOffset = startOffset; 639 this.endOffset = endOffset; 640 } 641 642 public boolean moveNext() { 643 synchronized (PositionsBag.this.marks) { 644 checkVersion(); 645 646 if (idx == -1) { 647 idx = indexBeforeOffset(startOffset); 648 if (idx == -1 && marks.size() > 0) { 649 idx = 0; 650 } 651 } else { 652 idx++; 653 } 654 655 while (isIndexValid(idx)) { 656 if (attributes.get(idx) != null) { 657 return true; 658 } 659 660 idx++; 662 } 663 664 return false; 665 } 666 } 667 668 public int getStartOffset() { 669 synchronized (PositionsBag.this.marks) { 670 assert idx != -1 : "Sequence not initialized, call moveNext() first."; checkVersion(); 672 673 if (isIndexValid(idx)) { 674 return Math.max(marks.get(idx).getOffset(), startOffset); 675 } else { 676 throw new NoSuchElementException (); 677 } 678 } 679 } 680 681 public int getEndOffset() { 682 synchronized (PositionsBag.this.marks) { 683 assert idx != -1 : "Sequence not initialized, call moveNext() first."; checkVersion(); 685 686 if (isIndexValid(idx)) { 687 return Math.min(marks.get(idx + 1).getOffset(), endOffset); 688 } else { 689 throw new NoSuchElementException (); 690 } 691 } 692 } 693 694 public AttributeSet getAttributes() { 695 synchronized (PositionsBag.this.marks) { 696 assert idx != -1 : "Sequence not initialized, call moveNext() first."; checkVersion(); 698 699 if (isIndexValid(idx)) { 700 return attributes.get(idx); 701 } else { 702 throw new NoSuchElementException (); 703 } 704 } 705 } 706 707 private boolean isIndexValid(int idx) { 708 return idx >= 0 && 709 idx + 1 < marks.size() && 710 marks.get(idx).getOffset() < endOffset && 711 marks.get(idx + 1).getOffset() > startOffset; 712 } 713 714 private void checkVersion() { 715 if (PositionsBag.this.version != version) { 716 throw new ConcurrentModificationException (); 717 } 718 } 719 } } 721 | Popular Tags |