1 19 20 package org.netbeans.modules.search; 21 22 import java.awt.EventQueue ; 23 import java.beans.PropertyChangeEvent ; 24 import java.beans.PropertyChangeListener ; 25 import java.io.File ; 26 import java.io.FileInputStream ; 27 import java.io.FileOutputStream ; 28 import java.io.IOException ; 29 import java.nio.ByteBuffer ; 30 import java.nio.CharBuffer ; 31 import java.nio.channels.ClosedByInterruptException ; 32 import java.nio.channels.FileChannel ; 33 import java.nio.charset.Charset ; 34 import java.util.Arrays ; 35 import java.util.List ; 36 import java.util.regex.Matcher ; 37 import java.util.regex.Pattern ; 38 import org.netbeans.modules.search.types.FullTextType; 39 import org.netbeans.modules.search.types.TextDetail; 40 import org.openide.filesystems.FileObject; 41 import org.openide.filesystems.FileUtil; 42 import org.openide.loaders.DataObject; 43 import org.openide.util.NbBundle; 44 45 52 final class MatchingObject implements PropertyChangeListener { 53 54 55 private final ResultModel resultModel; 56 57 private final File file; 58 59 private final long timestamp; 60 61 65 final Object object; 66 67 75 private boolean selected = true; 76 82 private boolean expanded = false; 83 91 private boolean[] selectedMatches; 92 99 private boolean childrenSelectionDirty; 100 101 private boolean valid = true; 102 103 private StringBuilder text; 104 105 108 boolean wasCrLf = false; 109 110 119 MatchingObject(ResultModel resultModel, Object object) { 120 if (resultModel == null) { 121 throw new IllegalArgumentException ("resultModel = null"); } 123 if (object == null) { 124 throw new IllegalArgumentException ("object = null"); } 126 127 this.resultModel = resultModel; 128 this.object = object; 129 130 FileObject fileObject = getFileObject(); 131 file = FileUtil.toFile(fileObject); 132 timestamp = (file != null) ? file.lastModified() : 0L; 133 valid = (timestamp != 0L); 134 135 setUpDataObjValidityChecking(); 136 } 137 138 140 private void setUpDataObjValidityChecking() { 141 final DataObject dataObj = (DataObject) object; 142 if (dataObj.isValid()) { 143 dataObj.addPropertyChangeListener(this); 144 } 145 } 146 147 149 void cleanup() { 150 final DataObject dataObj = (DataObject) object; 151 dataObj.removePropertyChangeListener(this); 152 } 153 154 public void propertyChange(PropertyChangeEvent e) { 155 if (DataObject.PROP_VALID.equals(e.getPropertyName()) 156 && Boolean.FALSE.equals(e.getNewValue())) { 157 assert e.getSource() == (DataObject) object; 158 159 final DataObject dataObj = (DataObject) object; 160 dataObj.removePropertyChangeListener(this); 161 162 resultModel.objectBecameInvalid(this); 163 } 164 } 165 166 173 boolean isObjectValid() { 174 return ((DataObject) object).isValid(); 175 } 176 177 private FileObject getFileObject() { 178 return ((DataObject) object).getPrimaryFile(); 179 } 180 181 183 void setSelected(boolean selected) { 184 if (selected == this.selected) { 185 return; 186 } 187 188 this.selected = selected; 189 selectedMatches = null; 190 } 191 192 194 boolean isSelected() { 195 return selected; 196 } 197 198 200 boolean isUniformSelection() { 201 return selectedMatches == null; 202 } 203 204 212 Boolean checkSubnodesSelection() { 213 if (selectedMatches == null) { 214 return Boolean.valueOf(selected); 215 } 216 217 final boolean firstMatchSelection = selectedMatches[0]; 218 for (int i = 1; i < selectedMatches.length; i++) { 219 if (selectedMatches[i] != firstMatchSelection) { 220 return null; 221 } 222 } 223 return Boolean.valueOf(firstMatchSelection); 224 } 225 226 228 void toggleSubnodeSelection(ResultModel resultModel, int index) { 229 if (selectedMatches == null) { 230 selectedMatches = new boolean[resultModel.getDetailsCount(this)]; 231 Arrays.fill(selectedMatches, this.selected); 232 } 233 selectedMatches[index] = !selectedMatches[index]; 234 } 235 236 238 void setSubnodeSelected(int index, 239 boolean selected, 240 ResultModel resultModel) { 241 if (selectedMatches == null) { 242 if (selected == this.selected) { 243 return; 244 } 245 selectedMatches = new boolean[resultModel.getDetailsCount(this)]; 246 Arrays.fill(selectedMatches, this.selected); 247 } 248 249 assert (index >= 0) && (index < selectedMatches.length); 250 selectedMatches[index] = selected; 251 } 252 253 255 boolean isSubnodeSelected(int index) { 256 assert (selectedMatches == null) 257 || ((index >= 0) && (index < selectedMatches.length)); 258 return (selectedMatches == null) ? selected 259 : selectedMatches[index]; 260 } 261 262 @Override 263 public boolean equals(Object anotherObject) { 264 return (anotherObject != null) 265 && (anotherObject.getClass() == MatchingObject.class) 266 && (((MatchingObject) anotherObject).object == this.object); 267 } 268 269 @Override 270 public int hashCode() { 271 return object.hashCode() + 1; 272 } 273 274 279 void markChildrenSelectionDirty() { 280 childrenSelectionDirty = true; 281 } 282 283 288 void markChildrenSelectionClean() { 289 childrenSelectionDirty = false; 290 } 291 292 298 boolean isChildrenSelectionDirty() { 299 return childrenSelectionDirty; 300 } 301 302 310 void markExpanded(boolean expanded) { 311 this.expanded = expanded; 312 } 313 314 322 boolean isExpanded() { 323 return expanded; 324 } 325 326 328 File getFile() { 329 return file; 330 } 331 332 333 String getName() { 334 return getFile().getName(); 335 } 336 337 339 long getTimestamp() { 340 return timestamp; 341 } 342 343 345 String getDescription() { 346 return getFile().getParent(); 347 } 348 349 351 String getText() throws IOException { 352 StringBuilder txt = text(); 353 if (txt != null) { 354 return txt.toString(); 355 } else { 356 return null; 357 } 358 } 359 360 366 private StringBuilder text() throws IOException { 367 return text(false); 368 } 369 370 private StringBuilder text(boolean refreshCache) throws IOException { 371 assert !EventQueue.isDispatchThread(); 372 373 if (refreshCache || (text == null)) { 374 text = readText(); 375 } 376 return text == null ? new StringBuilder () : text; 377 } 378 379 387 private StringBuilder readText() throws IOException { 388 StringBuilder ret = null; 389 390 ByteBuffer buf = getByteBuffer(); 391 if (buf != null) { 392 Charset charset = FullTextType.getCharset(getFileObject()); 393 CharBuffer cbuf = charset.decode(buf); 394 String terminator 395 = System.getProperty("line.separator"); 397 if (!terminator.equals("\n")) { Matcher matcher = Pattern.compile(terminator).matcher(cbuf); 399 if (matcher.find()) { 400 wasCrLf = true; 401 matcher.reset(); 402 ret = new StringBuilder ( 403 matcher.replaceAll("\n")); } 405 } 406 if (ret == null) { 407 ret = new StringBuilder (cbuf); 408 } 409 } 410 return ret; 411 } 412 413 417 private ByteBuffer getByteBuffer() throws IOException { 418 assert !EventQueue.isDispatchThread(); 419 420 File file = getFile(); 421 FileInputStream str = new FileInputStream (file); 424 425 ByteBuffer buffer = ByteBuffer.allocate((int) file.length()); 426 FileChannel channel = str.getChannel(); 427 try { 428 channel.read(buffer, 0); 429 } catch (ClosedByInterruptException cbie) { 430 return null; } finally { 432 channel.close(); 433 } 434 buffer.rewind(); 435 return buffer; 436 } 437 438 441 enum InvalidityStatus { 442 443 DELETED(true, "Inv_status_Err_deleted"), BECAME_DIR(true, "Inv_status_Err_became_dir"), CHANGED(false, "Inv_status_Err_changed"), TOO_BIG(false, "Inv_status_Err_too_big"), CANT_READ(false, "Inv_status_Err_cannot_read"), TOO_SHORT(false, "Inv_status_Err_too_short"); 450 455 private final boolean fatal; 456 461 private final String descrBundleKey; 462 463 469 private InvalidityStatus(boolean fatal, String descrBundleKey) { 470 this.fatal = fatal; 471 this.descrBundleKey = descrBundleKey; 472 } 473 474 478 boolean isFatal() { 479 return fatal; 480 } 481 482 489 String getDescription(String path) { 490 return NbBundle.getMessage(getClass(), descrBundleKey, path); 491 } 492 493 } 494 495 497 InvalidityStatus checkValidity() { 498 InvalidityStatus status = getInvalidityStatus(); 499 if (status != null) { 500 valid = false; 501 } 502 return status; 503 } 504 505 507 String getInvalidityDescription() { 508 String descr; 509 510 InvalidityStatus status = getInvalidityStatus(); 511 if (status != null) { 512 descr = status.getDescription(getFile().getPath()); 513 } else { 514 descr = null; 515 } 516 return descr; 517 } 518 519 527 private InvalidityStatus getInvalidityStatus() { 528 File f = getFile(); 529 if (!f.exists()) { 530 return InvalidityStatus.DELETED; 531 } 532 533 if (f.isDirectory()) { 534 return InvalidityStatus.BECAME_DIR; 535 } 536 537 long stamp = f.lastModified(); 538 if (stamp > resultModel.getCreationTime()) { 539 return InvalidityStatus.CHANGED; 540 } 541 542 if (f.length() > Integer.MAX_VALUE) { 543 return InvalidityStatus.TOO_BIG; 544 } 545 546 if (!f.canRead()) { 547 return InvalidityStatus.CANT_READ; 548 } 549 550 FullTextType fullTextType = resultModel.fullTextSearchType; 551 if ((fullTextType.getRe().length() == 0) 552 && f.length() < fullTextType.getMatchString().length()) { 553 return InvalidityStatus.TOO_SHORT; 554 } 555 556 return null; 557 } 558 559 561 boolean isValid() { 562 return valid; 563 } 564 565 567 public InvalidityStatus replace() throws IOException { 568 assert !EventQueue.isDispatchThread(); 569 assert isSelected(); 570 571 Boolean uniformSelection = checkSubnodesSelection(); 572 final boolean shouldReplaceAll = (uniformSelection == Boolean.TRUE); 573 final boolean shouldReplaceNone = (uniformSelection == Boolean.FALSE); 574 575 if (shouldReplaceNone) { 576 return null; 577 } 578 579 StringBuilder content = text(true); 581 List <TextDetail> textMatches = resultModel.fullTextSearchType 582 .getTextDetails(object); 583 int matchIndex = 0; 584 585 int currLineOffset = 0; 586 int currLine = 1; 587 588 int inlineMatchNumber = 0; int inlineOffsetShift = 0; 591 mainloop: 592 for (TextDetail textDetail : textMatches) { 593 int matchLine = textDetail.getLine(); 594 595 while (currLine < matchLine) { 596 int lfOffset = content.indexOf("\n",currLineOffset); if (lfOffset == -1) { 598 assert false; break mainloop; 600 } 601 602 currLineOffset = lfOffset + 1; currLine++; 604 inlineMatchNumber = 0; 605 inlineOffsetShift = 0; 606 } 607 608 if (!isSubnodeSelected(matchIndex++)) { 609 continue; 610 } 611 612 if (++inlineMatchNumber == 1) { boolean check = false; 614 assert check = true; if (check) { 616 int lineEndOffset = content.indexOf("\n", currLineOffset); 618 String fileLine = (lineEndOffset != -1) 619 ? content.substring(currLineOffset, 620 lineEndOffset) 621 : content.substring(currLineOffset); 622 if (!fileLine.equals(textDetail.getLineText())) { 623 return InvalidityStatus.CHANGED; 624 } 625 } 626 } 627 628 int matchLength = textDetail.getMarkLength(); 629 int matchOffset = currLineOffset + inlineOffsetShift 630 + (textDetail.getColumn() - 1); 631 int matchEndOffset = matchOffset + matchLength; 632 if (!content.substring(matchOffset, matchEndOffset) 633 .equals(textDetail.getLineText().substring( 634 textDetail.getColumn() - 1, 635 textDetail.getColumn() - 1 + matchLength))) { 636 return InvalidityStatus.CHANGED; 637 } 638 639 content.replace(matchOffset, matchEndOffset, 640 resultModel.replaceString); 641 inlineOffsetShift += resultModel.replaceString.length() 642 - matchLength; 643 } 644 return null; 645 } 646 647 648 private static final boolean REALLY_WRITE = true; 649 650 652 void write() throws IOException { 653 if (text == null) { 654 throw new IllegalStateException ("Buffer is gone"); } 656 657 if (REALLY_WRITE) { 658 if (wasCrLf) { 659 String terminator 660 = System.getProperty("line.separator"); text = new StringBuilder ( 663 text.toString().replace("\n", terminator)); } 665 Charset charset = FullTextType.getCharset(getFileObject()); 666 ByteBuffer buffer = charset.encode(text.toString()); 667 668 FileOutputStream fos = new FileOutputStream (getFile()); 669 FileChannel channel = fos.getChannel(); 670 channel.write(buffer); 671 channel.close(); 675 } else { 676 System.err.println("Would write to " + getFile().getPath()); System.err.println(text); 678 } 679 } 680 681 684 public String toString() { 685 return super.toString() + "[" + getName()+ "]"; } 687 } 688 | Popular Tags |