1 19 package org.netbeans.core.output2; 20 21 import java.io.File ; 22 import java.io.FileOutputStream ; 23 import java.io.IOException ; 24 import java.nio.ByteBuffer ; 25 import java.nio.CharBuffer ; 26 import java.nio.channels.FileChannel ; 27 import java.nio.charset.CharacterCodingException ; 28 import java.nio.charset.Charset ; 29 import java.nio.charset.CharsetEncoder ; 30 import java.util.logging.Logger ; 31 import java.util.regex.Matcher ; 32 import java.util.regex.Pattern ; 33 import javax.swing.event.ChangeEvent ; 34 import javax.swing.event.ChangeListener ; 35 import org.openide.util.Exceptions; 36 import org.openide.util.Mutex; 37 import org.openide.windows.OutputListener; 38 39 42 abstract class AbstractLines implements Lines, Runnable { 43 44 IntList lineStartList; 45 46 IntMap linesToListeners; 47 private int longestLine = 0; 48 private static Boolean unitTestUseCache = null; 49 private int knownCharCount = -1; 50 private SparseIntList knownLogicalLineCounts = null; 51 private int lastCharCountForWrapCalculation = -1; 52 private int lastWrappedLineCount = -1; 53 private int lastCharCountForWrapAboveCalculation = -1; 54 private int lastWrappedAboveLineCount = -1; 55 private int lastWrappedAboveLine = -1; 56 private IntList errLines = null; 57 58 59 AbstractLines() { 60 if (Controller.LOG) Controller.log ("Creating a new AbstractLines"); 61 init(); 62 } 63 64 protected abstract Storage getStorage(); 65 66 protected abstract boolean isDisposed(); 67 68 protected abstract boolean isTrouble(); 69 70 protected abstract void handleException (Exception e); 71 72 public char[] getText (int start, int end, char[] chars) { 73 if (isDisposed() || isTrouble()) { 74 if (Controller.LOG) { 79 Controller.log (this + " !!!!!REQUEST FOR SUBRANGE " + start + "-" + end + " AFTER OUTWRITER HAS BEEN DISPOSED!!"); 80 81 } 82 char[] msg = "THIS OUTPUT HAS BEEN DISPOSED! ".toCharArray(); 83 if (chars == null) { 84 chars = new char[end - start]; 85 } 86 int pos = 0; 87 for (int i=0; i < chars.length; i++) { 88 if (pos == msg.length - 1) { 89 pos = 0; 90 } 91 chars[i] = msg[pos]; 92 pos++; 93 } 94 return chars; 95 } 96 if (end < start) { 97 throw new IllegalArgumentException ("Illogical text range from " + 98 start + " to " + end); 99 } 100 synchronized(readLock()) { 101 int fileStart = AbstractLines.toByteIndex(start); 102 int byteCount = AbstractLines.toByteIndex(end - start); 103 try { 104 CharBuffer chb = getStorage().getReadBuffer(fileStart, byteCount).asCharBuffer(); 105 int len = Math.min(end - start, chb.remaining()); 108 if (chars.length < len) { 109 chars = new char[len]; 110 } 111 chb.get(chars, 0, len); 112 return chars; 113 } catch (Exception e) { 114 handleException (e); 115 return new char[0]; 116 } 117 } 118 } 119 120 public String getText (int start, int end) { 121 if (isDisposed() || isTrouble()) { 122 return new String (new char[end - start]); 123 } 124 if (end < start) { 125 throw new IllegalArgumentException ("Illogical text range from " + 126 start + " to " + end); 127 } 128 synchronized(readLock()) { 129 int fileStart = AbstractLines.toByteIndex(start); 130 int byteCount = AbstractLines.toByteIndex(end - start); 131 int available = getStorage().size(); 132 if (available < fileStart + byteCount) { 133 throw new ArrayIndexOutOfBoundsException ("Bytes from " + 134 fileStart + " to " + (fileStart + byteCount) + " requested, " + 135 "but storage is only " + available + " bytes long"); 136 } 137 try { 138 return getStorage().getReadBuffer(fileStart, byteCount).asCharBuffer().toString(); 139 } catch (Exception e) { 140 handleException (e); 141 return new String (new char[end - start]); 142 } 143 } 144 } 145 146 private int lastErrLineMarked = -1; 147 void markErr() { 148 if (isTrouble() || getStorage().isClosed()) { 149 return; 150 } 151 if (errLines == null) { 152 errLines = new IntList(20); 153 } 154 int linecount = getLineCount(); 155 if (linecount != lastErrLineMarked) { 157 errLines.add(linecount == 0 ? 0 : linecount-1); 158 lastErrLineMarked = linecount; 159 } 160 } 161 162 public boolean isErr(int line) { 163 return errLines != null ? errLines.contains(line) : false; 164 } 165 166 private ChangeListener listener = null; 167 public void addChangeListener(ChangeListener cl) { 168 this.listener = cl; 169 synchronized(this) { 170 if (getLineCount() > 0) { 171 fire(); 173 } 174 } 175 } 176 177 public void removeChangeListener (ChangeListener cl) { 178 if (listener == cl) { 179 listener = null; 180 } 181 } 182 183 public void fire() { 184 if (isTrouble()) { 185 return; 186 } 187 if (Controller.LOG) Controller.log (this + ": Writer firing " + getStorage().size() + " bytes written"); 188 if (listener != null) { 189 Mutex.EVENT.readAccess(this); 190 } 191 } 192 193 public void run() { 194 if (listener != null) { 195 listener.stateChanged(new ChangeEvent (this)); 196 } 197 } 198 199 public boolean hasHyperlinks() { 200 return firstListenerLine() != -1; 201 } 202 203 public boolean isHyperlink (int line) { 204 return getListenerForLine(line) != null; 205 } 206 207 private void init() { 208 knownLogicalLineCounts = null; 209 lineStartList = new IntList(100); 210 linesToListeners = new IntMap(); 211 setLastWrappedLineCount(-1); 212 longestLine = 0; 213 errLines = null; 214 matcher = null; 215 listener = null; 216 dirty = false; 217 } 218 219 private boolean dirty; 220 221 public boolean checkDirty(boolean clear) { 222 if (isTrouble()) { 223 return false; 224 } 225 boolean wasDirty = dirty; 226 if (clear) { 227 dirty = false; 228 } 229 return wasDirty; 230 } 231 232 public int[] allListenerLines() { 233 return linesToListeners.getKeys(); 234 } 235 236 void clear() { 237 init(); 238 } 239 240 public int getCharCount() { 241 if (isDisposed() || isTrouble()) return 0; 242 Storage storage = getStorage(); 243 return storage == null ? 0 : AbstractLines.toCharIndex(getStorage().size()); 244 } 245 246 249 public String getLine (int idx) throws IOException { 250 if (isDisposed() || isTrouble()) { 251 return ""; } 253 int lineStart = lineStartList.get(idx); 254 int lineEnd; 255 if (idx != lineStartList.size()-1) { 256 lineEnd = lineStartList.get(idx+1); 257 } else { 258 lineEnd = getStorage().size(); 259 } 260 CharBuffer cb = getStorage().getReadBuffer(lineStart, 261 lineEnd - lineStart).asCharBuffer(); 262 263 char chars[] = new char[cb.limit()]; 264 cb.get (chars); 265 return new String (chars); 266 } 267 268 271 private int getLineLength(int idx) { 272 int lineStart = lineStartList.get(idx); 273 int lineEnd; 274 if (idx != lineStartList.size()-1) { 275 lineEnd = lineStartList.get(idx+1); 276 } else { 277 lineEnd = getStorage().size(); 278 } 279 return lineEnd - lineStart; 280 } 281 282 public boolean isLineStart (int chpos) { 283 int bpos = toByteIndex(chpos); 284 return lineStartList.contains (bpos); 285 } 286 287 293 public int length (int idx) { 294 if (isDisposed() || isTrouble()) { 295 return 0; 296 } 297 if (lineStartList.size() == 0) { 298 return 0; 299 } 300 int lineStart = lineStartList.get(idx); 301 int lineEnd; 302 if (idx != lineStartList.size()-1) { 303 lineEnd = lineStartList.get(idx+1); 304 } else { 305 lineEnd = getStorage().size(); 306 } 307 return toCharIndex(lineEnd - lineStart); 308 } 309 310 314 public int getLineStart (int line) { 315 if (isDisposed() || isTrouble()) return 0; 316 if (lineStartList.size() == 0) return 0; 317 return toCharIndex(lineStartList.get(line)); 318 } 319 320 323 public int getLineAt (int position) { 324 if (isDisposed() || isTrouble()) return -1; 325 int bytePos = toByteIndex (position); 326 int i = lineStartList.indexOf (bytePos); 327 if (i != -1) { 328 return i; 329 } 330 return lineStartList.findNearest(bytePos); 331 } 332 333 public int getLineCount() { 334 if (isDisposed() || isTrouble()) return 0; 335 return lineStartList.size(); 336 } 337 338 public OutputListener getListenerForLine (int line) { 339 return (OutputListener) linesToListeners.get(line); 340 } 341 342 public int firstListenerLine () { 343 if (isDisposed() || isTrouble()) return -1; 344 return linesToListeners.isEmpty() ? -1 : linesToListeners.first(); 345 } 346 347 public int nearestListenerLine (int line, boolean backward) { 348 if (isDisposed() || isTrouble()) return -1; 349 return linesToListeners.nearest (line, backward); 350 } 351 352 public int getLongestLineLength() { 353 return toCharIndex(longestLine); 354 } 355 356 357 public void toLogicalLineIndex (final int[] physIdx, int charsPerLine) { 358 int physicalLine = physIdx[0]; 359 physIdx[1] = 0; 360 361 if (physicalLine == 0) { 362 physIdx[1] = 0; 364 physIdx[2] = (length(physicalLine) / charsPerLine); 365 } 366 367 if (charsPerLine >= getLongestLineLength() || (getLineCount() <= 1)) { 368 physIdx[1] = 0; 370 physIdx[2] = 1; 371 return; 372 } 373 374 int logicalLine = 375 findFirstLineWithoutMoreLinesAboveItThan (physicalLine, charsPerLine); 376 377 int linesAbove = getLogicalLineCountAbove(logicalLine, charsPerLine); 378 379 int len = length(logicalLine); 380 381 int wrapCount = len > charsPerLine ? (len / charsPerLine) + 1 : 1; 382 383 physIdx[0] = logicalLine; 384 int lcount = linesAbove + wrapCount; 385 physIdx[1] = (wrapCount - (lcount - physicalLine)); 386 387 physIdx[2] = wrapCount; 388 } 389 390 398 private int findFirstLineWithoutMoreLinesAboveItThan (int target, int charsPerLine) { 399 int start = 0; 400 int end = getLineCount(); 401 int midpoint = start + ((end - start) >> 1); 402 int linesAbove = getLogicalLineCountAbove(midpoint, charsPerLine); 403 int result = divideAndConquer (target, start, midpoint, end, charsPerLine, linesAbove); 404 405 return Math.min(end, result) -1; 406 } 407 417 private int divideAndConquer (int target, int start, int midpoint, int end, int charsPerLine, int midValue) { 418 if (midValue == target) { 420 return midpoint + 1; 421 } 422 423 if (end - start <= 1 || midpoint == start || midpoint == end) { 426 return end; 427 } 428 429 if (midValue > target) { 430 433 int upperMidPoint = start + ((midpoint - start) >> 1); 434 if ((midpoint - start) % 2 != 0) { 435 upperMidPoint++; 436 } 437 int upperMidValue = getLogicalLineCountAbove(upperMidPoint, charsPerLine); 438 return divideAndConquer (target, start, upperMidPoint, midpoint, charsPerLine, upperMidValue); 439 } else { 440 443 int lowerMidPoint = ((end - start) >> 2) + midpoint; 444 if ((end - midpoint) % 2 != 0) { 445 lowerMidPoint++; 446 } 447 int lowerMidValue = getLogicalLineCountAbove(lowerMidPoint, charsPerLine); 448 return divideAndConquer (target, midpoint, lowerMidPoint, end, charsPerLine, lowerMidValue); 449 } 450 } 451 452 453 458 public int getLogicalLineCountAbove (int line, int charCount) { 459 if (line == 0) { 460 return 0; 461 } 462 if (toByteIndex(charCount) > longestLine) { 463 return line; 464 } 465 466 if (unitTestUseCache != null) { 468 if (Boolean.TRUE.equals(unitTestUseCache)) { 469 return dynLogicalLineCountAbove(line, charCount); 470 } else { 471 return cachedLogicalLineCountAbove (line, charCount); 472 } 473 } 474 475 if (getStorage().size() < 100000) { 476 return dynLogicalLineCountAbove(line, charCount); 477 } else { 478 return cachedLogicalLineCountAbove (line, charCount); 479 } 480 } 481 482 488 public int getLogicalLineCountIfWrappedAt (int charCount) { 489 if (toByteIndex(charCount) > longestLine) { 490 return getLineCount(); 491 } 492 493 if (unitTestUseCache != null) { 494 if (Boolean.TRUE.equals(unitTestUseCache)) { 495 return dynLogicalLineCountIfWrappedAt(charCount); 496 } else { 497 return cachedLogicalLineCountIfWrappedAt(charCount); 498 } 499 } 500 501 if (getStorage().size() < 100000) { 502 return dynLogicalLineCountIfWrappedAt(charCount); 503 } else { 504 return cachedLogicalLineCountIfWrappedAt(charCount); 505 } 506 } 507 508 public void addListener (int line, OutputListener l, boolean important) { 509 if (l == null) { 510 Logger.getAnonymousLogger().warning("Issue #56826 - Adding a null OutputListener for line:" + line); 512 } else { 513 linesToListeners.put(line, l); 514 if (important) { 515 importantLines.add(line); 516 } 517 } 518 } 519 520 private IntList importantLines = new IntList(10); 521 522 public int firstImportantListenerLine() { 523 return importantLines.size() == 0 ? -1 : importantLines.get(0); 524 } 525 526 public boolean isImportantHyperlink(int line) { 527 return importantLines.contains(line); 528 } 529 530 534 static void unitTestUseCache (Boolean val) { 535 unitTestUseCache = val; 536 } 537 538 private int cachedLogicalLineCountAbove (int line, int charCount) { 539 if (charCount != knownCharCount || knownLogicalLineCounts == null) { 540 knownCharCount = charCount; 541 calcCharCounts(charCount); 542 } 543 return knownLogicalLineCounts.get(line); 544 } 545 546 private int cachedLogicalLineCountIfWrappedAt (int charCount) { 547 int lineCount = getLineCount(); 548 if (charCount == 0 || lineCount == 0) { 549 return 0; 550 } 551 synchronized (readLock()) { 552 if (charCount != knownCharCount || knownLogicalLineCounts == null) { 553 knownCharCount = charCount; 554 calcCharCounts(charCount); 555 } 556 int result = knownLogicalLineCounts.get(lineCount-1); 557 int len = length (lineCount - 1); 558 result += (len / charCount) + 1; 559 return result; 560 } 561 } 562 563 573 private void calcCharCounts(int width) { 574 synchronized (readLock()) { 575 int lineCount = getLineCount(); 576 knownLogicalLineCounts = new SparseIntList(30); 577 578 int val = 0; 579 for (int i=1; i < lineCount; i++) { 580 int len = length(i); 581 582 if (len > width) { 583 val += (len / width) + 1; 584 knownLogicalLineCounts.add(val, i); 585 } else { 586 val++; 587 } 588 } 589 590 knownCharCount = width; 591 } 592 } 593 594 598 private int dynLogicalLineCountIfWrappedAt (int charCount) { 599 600 synchronized (readLock()) { 601 int bcount = toByteIndex(charCount); 602 if (longestLine <= bcount) { 603 return lineStartList.size(); 604 } 605 if (charCount == lastCharCountForWrapCalculation && lastWrappedLineCount != -1) { 606 return lastWrappedLineCount; 607 } 608 if (lineStartList.size() == 0) { 609 return 0; 610 } 611 int nOfLines = lineStartList.size(); 612 int lineCount = 0; 613 for (int i=0; i < nOfLines; i++) { 614 int currLength = getLineLength(i); 615 lineCount += (currLength > bcount) ? ((currLength / bcount) + 1) : 1; 616 } 617 setLastCharCountForWrapCalculation(charCount); 618 lastWrappedLineCount = lineCount; 619 return lineCount; 620 } 621 } 622 623 631 private int dynLogicalLineCountAbove (int line, int charCount) { 632 int lineCount = getLineCount(); 633 if (line == 0 || lineCount == 0) { 634 return 0; 635 } 636 if (charCount == lastCharCountForWrapAboveCalculation && lastWrappedAboveLineCount != -1 && line == lastWrappedAboveLine) { 637 return lastWrappedAboveLineCount; 638 } 639 640 synchronized (readLock()) { 641 lastWrappedAboveLineCount = 0; 642 643 for (int i=0; i < line; i++) { 644 int len = length(i); 645 if (len > charCount) { 646 lastWrappedAboveLineCount += (len / charCount) + 1; 647 } else { 648 lastWrappedAboveLineCount++; 649 } 650 } 651 652 lastWrappedAboveLine = line; 653 setLastCharCountForWrapAboveCalculation(charCount); 654 return lastWrappedAboveLineCount; 655 } 656 } 657 658 void markDirty() { 659 dirty = true; 660 } 661 662 public void lineStarted(int start) { 663 if (Controller.VERBOSE) Controller.log("AbstractLines.lineStarted " + start); int lineCount = 0; 665 synchronized (readLock()) { 666 setLastWrappedLineCount(-1); 667 setLastCharCountForWrapAboveCalculation(-1); 668 lineStartList.add(start); 669 matcher = null; 670 lineCount = lineStartList.size(); 671 } 672 if (lineCount == 20 || lineCount == 10 || lineCount == 1) { 673 if (Controller.LOG) Controller.log("Firing initial write event"); 675 fire(); 676 } 677 } 678 679 public void lineFinished(int lineLength) { 680 synchronized (readLock()) { 681 setLastWrappedLineCount(-1); 682 setLastCharCountForWrapAboveCalculation(-1); 683 longestLine = Math.max(longestLine, lineLength); 684 matcher = null; 685 686 int lineCount = lineStartList.size(); 687 int lastline = lineCount-1; 689 checkLogicalLineCount(lastline); 690 } 691 } 692 693 private void checkLogicalLineCount(int lastline) { 694 if (knownLogicalLineCounts != null) { 697 int len = length(lastline); 699 700 if (len > knownCharCount) { 703 int aboveLineCount; 704 if (knownLogicalLineCounts.lastIndex() != -1) { 705 aboveLineCount = (lastline - (knownLogicalLineCounts.lastIndex() + 1)) + knownLogicalLineCounts.lastAdded(); 709 } else { 710 aboveLineCount = Math.max(0, lastline-1); 713 } 714 aboveLineCount += (len / knownCharCount) + 1; 716 knownLogicalLineCounts.add(aboveLineCount, lastline); 717 } 718 } 719 720 } 721 722 public void lineWritten(int start, int lineLength) { 723 if (Controller.VERBOSE) Controller.log("AbstractLines.lineWritten " + start + " length:" + lineLength); int lineCount = 0; 725 synchronized (readLock()) { 726 setLastWrappedLineCount(-1); 727 setLastCharCountForWrapAboveCalculation(-1); 728 longestLine = Math.max(longestLine, lineLength); 729 lineStartList.add(start); 730 matcher = null; 731 732 lineCount = lineStartList.size(); 733 int lastline = lineCount-1; 735 checkLogicalLineCount(lastline); 736 737 } 738 if (lineCount == 20 || lineCount == 10 || lineCount == 1) { 739 if (Controller.LOG) Controller.log("Firing initial write event"); 741 fire(); 742 } 743 } 744 745 747 static int toByteIndex (int charIndex) { 748 return charIndex << 1; 749 } 750 751 753 static int toCharIndex (int byteIndex) { 754 assert byteIndex % 2 == 0 : "bad index: " + byteIndex; return byteIndex >> 1; 756 } 757 758 public void saveAs(String path) throws IOException { 759 if (getStorage()== null) { 760 throw new IOException ("Data has already been disposed"); } 762 File f = new File (path); 763 CharBuffer cb = getStorage().getReadBuffer(0, getStorage().size()).asCharBuffer(); 764 765 FileOutputStream fos = new FileOutputStream (f); 766 try { 767 String encoding = System.getProperty ("file.encoding"); if (encoding == null) { 769 encoding = "UTF-8"; } 771 Charset charset = Charset.forName (encoding); CharsetEncoder encoder = charset.newEncoder (); 773 ByteBuffer bb = encoder.encode (cb); 774 FileChannel ch = fos.getChannel(); 775 ch.write(bb); 776 ch.close(); 777 } finally { 778 fos.close(); 779 } 780 } 781 782 private String lastSearchString = null; 783 private Matcher matcher = null; 784 public Matcher getForwardMatcher() { 785 return matcher; 786 } 787 788 public Matcher getReverseMatcher() { 789 try { 790 Storage storage = getStorage(); 791 if (storage == null) { 792 return null; 793 } 794 if (matcher != null && lastSearchString != null && lastSearchString.length() > 0 && storage.size() > 0) { 795 StringBuffer sb = new StringBuffer (lastSearchString); 796 sb.reverse(); 797 CharBuffer buf = storage.getReadBuffer(0, storage.size()).asCharBuffer(); 798 StringBuffer data = new StringBuffer (buf.toString()); 800 data.reverse(); 801 802 Pattern pat = escapePattern(sb.toString()); 803 return pat.matcher(data); 804 } 805 } catch (Exception e) { 806 Exceptions.printStackTrace(e); 807 } 808 return null; 809 } 810 811 public Matcher find(String s) { 812 if (Controller.LOG) Controller.log (this + ": Executing find for string " + s + " on " ); 813 Storage storage = getStorage(); 814 if (storage == null) { 815 return null; 816 } 817 if (matcher != null && s.equals(lastSearchString)) { 818 return matcher; 819 } 820 try { 821 int size = storage.size(); 822 if (size > 0) { 823 Pattern pat = escapePattern(s); 824 CharBuffer buf = storage.getReadBuffer(0, size).asCharBuffer(); 825 Matcher m = pat.matcher(buf); 826 if (!m.find(0)) { 827 return null; 828 } 829 matcher = m; 830 matcher.reset(); 831 lastSearchString = s; 832 return matcher; 833 } 834 } catch (IOException ioe) { 835 Exceptions.printStackTrace(ioe); 836 } 837 return null; 838 } 839 840 844 static Pattern escapePattern(String s) { 845 String replacement = s.replaceAll("([\\(\\)\\[\\]\\^\\*\\.\\$\\{\\}\\?\\+\\\\])", "\\\\$1"); 849 return Pattern.compile(replacement, Pattern.CASE_INSENSITIVE); 850 } 851 852 private void setLastCharCountForWrapCalculation(int lastCharCountForWrapCalculation) { 853 this.lastCharCountForWrapCalculation = lastCharCountForWrapCalculation; 854 } 855 856 private void setLastWrappedLineCount(int lastWrappedLineCount) { 857 this.lastWrappedLineCount = lastWrappedLineCount; 858 } 859 860 private void setLastCharCountForWrapAboveCalculation(int lastCharCountForWrapAboveCalculation) { 861 this.lastCharCountForWrapAboveCalculation = lastCharCountForWrapAboveCalculation; 862 } 863 864 public String toString() { 865 return lineStartList.toString(); 866 } 867 } 868 | Popular Tags |