1 /******************************************************************************* 2 * Copyright (c) 2016 Google, Inc and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * Stefan Xenos (Google) - Initial implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.core.nd.indexer; 15 16 import static org.eclipse.jdt.internal.compiler.util.Util.UTF_8; 17 import static org.eclipse.jdt.internal.compiler.util.Util.getInputStreamAsCharArray; 18 19 import java.io.FileNotFoundException; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.text.DecimalFormat; 23 import java.text.SimpleDateFormat; 24 import java.util.ArrayList; 25 import java.util.Collection; 26 import java.util.Collections; 27 import java.util.Date; 28 import java.util.Enumeration; 29 import java.util.HashMap; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Map.Entry; 34 import java.util.Set; 35 import java.util.WeakHashMap; 36 import java.util.zip.ZipEntry; 37 import java.util.zip.ZipException; 38 import java.util.zip.ZipFile; 39 40 import org.eclipse.core.resources.IProject; 41 import org.eclipse.core.resources.IResource; 42 import org.eclipse.core.resources.IWorkspaceRoot; 43 import org.eclipse.core.resources.ResourcesPlugin; 44 import org.eclipse.core.runtime.CoreException; 45 import org.eclipse.core.runtime.IPath; 46 import org.eclipse.core.runtime.IProgressMonitor; 47 import org.eclipse.core.runtime.OperationCanceledException; 48 import org.eclipse.core.runtime.Path; 49 import org.eclipse.core.runtime.Platform; 50 import org.eclipse.core.runtime.SubMonitor; 51 import org.eclipse.core.runtime.jobs.Job; 52 import org.eclipse.core.runtime.jobs.JobGroup; 53 import org.eclipse.jdt.core.IJavaElement; 54 import org.eclipse.jdt.core.IJavaElementDelta; 55 import org.eclipse.jdt.core.IJavaModelStatusConstants; 56 import org.eclipse.jdt.core.IJavaProject; 57 import org.eclipse.jdt.core.IOrdinaryClassFile; 58 import org.eclipse.jdt.core.IPackageFragmentRoot; 59 import org.eclipse.jdt.core.JavaCore; 60 import org.eclipse.jdt.core.JavaModelException; 61 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; 62 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; 63 import org.eclipse.jdt.internal.compiler.env.IDependent; 64 import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; 65 import org.eclipse.jdt.internal.compiler.util.SuffixConstants; 66 import org.eclipse.jdt.internal.core.JarPackageFragmentRoot; 67 import org.eclipse.jdt.internal.core.JavaElementDelta; 68 import org.eclipse.jdt.internal.core.JavaModel; 69 import org.eclipse.jdt.internal.core.JavaModelManager; 70 import org.eclipse.jdt.internal.core.nd.IReader; 71 import org.eclipse.jdt.internal.core.nd.Nd; 72 import org.eclipse.jdt.internal.core.nd.db.Database; 73 import org.eclipse.jdt.internal.core.nd.db.IndexException; 74 import org.eclipse.jdt.internal.core.nd.java.FileFingerprint; 75 import org.eclipse.jdt.internal.core.nd.java.FileFingerprint.FingerprintTestResult; 76 import org.eclipse.jdt.internal.core.nd.java.JavaIndex; 77 import org.eclipse.jdt.internal.core.nd.java.JavaNames; 78 import org.eclipse.jdt.internal.core.nd.java.NdResourceFile; 79 import org.eclipse.jdt.internal.core.nd.java.NdType; 80 import org.eclipse.jdt.internal.core.nd.java.NdTypeId; 81 import org.eclipse.jdt.internal.core.nd.java.NdWorkspaceLocation; 82 import org.eclipse.jdt.internal.core.nd.java.TypeRef; 83 import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeDescriptor; 84 import org.eclipse.jdt.internal.core.nd.java.model.BinaryTypeFactory; 85 import org.eclipse.jdt.internal.core.nd.java.model.IndexBinaryType; 86 import org.eclipse.jdt.internal.core.search.processing.IJob; 87 88 public final class Indexer { 89 private Nd nd; 90 private IWorkspaceRoot root; 91 92 private static Indexer indexer; 93 public static boolean DEBUG; 94 public static boolean DEBUG_ALLOCATIONS; 95 public static boolean DEBUG_TIMING; 96 public static boolean DEBUG_SCHEDULING; 97 public static boolean DEBUG_INSERTIONS; 98 public static boolean DEBUG_SELFTEST; 99 public static int DEBUG_LOG_SIZE_MB; 100 101 // New index is disabled, see bug 544898 102 // private static final String ENABLE_NEW_JAVA_INDEX = "enableNewJavaIndex"; //$NON-NLS-1$ 103 // private static IPreferenceChangeListener listener = new IPreferenceChangeListener() { 104 // @Override 105 // public void preferenceChange(PreferenceChangeEvent event) { 106 // if (ENABLE_NEW_JAVA_INDEX.equals(event.getKey())) { 107 // if (JavaIndex.isEnabled()) { 108 // getInstance().rescanAll(); 109 // } else { 110 // ChunkCache.getSharedInstance().clear(); 111 // } 112 // } 113 // } 114 // }; 115 116 // This is an arbitrary constant that is larger than the maximum number of ticks 117 // reported by SubMonitor and small enough that it won't overflow a long when multiplied by a large 118 // database size. 119 private final static int TOTAL_TICKS_TO_REPORT_DURING_INDEXING = 1000; 120 121 /** 122 * True iff automatic reindexing (that is, the {@link #rescanAll()} method) is disabled. Synchronize on 123 * {@link #automaticIndexingMutex} while accessing. 124 */ 125 private boolean enableAutomaticIndexing = true; 126 /** 127 * True iff any code tried to schedule reindexing while automatic reindexing was disabled. Synchronize on 128 * {@link #automaticIndexingMutex} while accessing. 129 */ 130 private boolean indexerDirtiedWhileDisabled = false; 131 private final Object automaticIndexingMutex = new Object(); 132 133 private final FileStateCache fileStateCache; 134 private static final Object mutex = new Object(); 135 136 private Object listenersMutex = new Object(); 137 /** 138 * Listener list. Copy-on-write. Synchronize on "listenersMutex" before accessing. 139 */ 140 private Set<Listener> listeners = Collections.newSetFromMap(new WeakHashMap<Listener, Boolean>()); 141 142 private JobGroup group = new JobGroup(Messages.Indexer_updating_index_job_name, 1, 1); 143 144 private Job rescanJob = Job.create(Messages.Indexer_updating_index_job_name, monitor -> { 145 SubMonitor subMonitor = SubMonitor.convert(monitor); 146 try { 147 rescan(subMonitor); 148 } catch (IndexException e) { 149 Package.log("Database corruption detected during indexing. Deleting and rebuilding the index.", e); //$NON-NLS-1$ 150 // If we detect corruption during indexing, delete and rebuild the entire index 151 rebuildIndex(subMonitor); 152 } 153 }); 154 155 private Job rebuildIndexJob = Job.create(Messages.Indexer_updating_index_job_name, monitor -> { 156 rebuildIndex(monitor); 157 }); 158 159 public static interface Listener { consume(IndexerEvent event)160 void consume(IndexerEvent event); 161 } 162 getInstance()163 public static Indexer getInstance() { 164 synchronized (mutex) { 165 if (indexer == null) { 166 indexer = new Indexer(JavaIndex.getGlobalNd(), ResourcesPlugin.getWorkspace().getRoot()); 167 // IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(JavaCore.PLUGIN_ID); 168 // preferences.addPreferenceChangeListener(listener); 169 } 170 return indexer; 171 } 172 } 173 174 /** 175 * Enables or disables the "rescanAll" method. When set to false, rescanAll does nothing 176 * and indexing will only be triggered when invoking {@link #waitForIndex}. 177 * <p> 178 * Normally the indexer runs automatically and asynchronously when resource changes occur. 179 * However, if this variable is set to false the indexer only runs when someone invokes 180 * {@link #waitForIndex(IProgressMonitor)}. This can be used to eliminate race conditions 181 * when running the unit tests, since indexing will not occur unless it is triggered 182 * explicitly. 183 * <p> 184 * Synchronize on {@link #automaticIndexingMutex} before accessing. 185 */ enableAutomaticIndexing(boolean enabled)186 public void enableAutomaticIndexing(boolean enabled) { 187 boolean runRescan = false; 188 synchronized (this.automaticIndexingMutex) { 189 if (this.enableAutomaticIndexing == enabled) { 190 return; 191 } 192 this.enableAutomaticIndexing = enabled; 193 if (enabled && this.indexerDirtiedWhileDisabled) { 194 runRescan = true; 195 } 196 } 197 198 if (JavaIndex.isEnabled()) { 199 if (runRescan) { 200 // Force a rescan when re-enabling automatic indexing since we may have missed an update 201 this.rescanJob.schedule(); 202 } 203 204 if (!enabled) { 205 // Wait for any existing indexing operations to finish when disabling automatic indexing since 206 // we only want explicitly-triggered indexing operations to run after the method returns 207 try { 208 this.rescanJob.join(0, null); 209 } catch (OperationCanceledException | InterruptedException e) { 210 // Don't care 211 } 212 } 213 } 214 } 215 216 /** 217 * Amount of time (milliseconds) unreferenced files are allowed to sit in the index before they are discarded. 218 * Making this too short will cause some operations (classpath modifications, closing/reopening projects, etc.) 219 * to become more expensive. Making this too long will waste space in the database. 220 * <p> 221 * The value of this is stored in the JDT core preference called "garbageCleanupTimeoutMs". The default value 222 * is 3 days. 223 */ getGarbageCleanupTimeout()224 private static long getGarbageCleanupTimeout() { 225 return Platform.getPreferencesService().getLong(JavaCore.PLUGIN_ID, "garbageCleanupTimeoutMs", //$NON-NLS-1$ 226 1000 * 60 * 60 * 24 * 3, 227 null); 228 } 229 230 /** 231 * Amount of time (milliseconds) before we update the "used" timestamp on a file in the index. We don't update 232 * the timestamps every update since doing so would be unnecessarily inefficient... but if any of the timestamps 233 * is older than this update period, we refresh it. 234 */ getUsageTimestampUpdatePeriod()235 private static long getUsageTimestampUpdatePeriod() { 236 return getGarbageCleanupTimeout() / 4; 237 } 238 rescan(IProgressMonitor monitor)239 public void rescan(IProgressMonitor monitor) throws CoreException { 240 SubMonitor subMonitor = SubMonitor.convert(monitor, 100); 241 Database db = this.nd.getDB(); 242 db.resetCacheCounters(); 243 db.getLog().setBufferSize(DEBUG_LOG_SIZE_MB); 244 245 synchronized (this.automaticIndexingMutex) { 246 this.indexerDirtiedWhileDisabled = false; 247 } 248 249 long currentTimeMs = System.currentTimeMillis(); 250 if (DEBUG) { 251 Package.logInfo("Indexer running rescan"); //$NON-NLS-1$ 252 } 253 254 this.fileStateCache.clear(); 255 WorkspaceSnapshot snapshot = WorkspaceSnapshot.create(this.root, subMonitor.split(1)); 256 Set<IPath> locations = snapshot.allLocations(); 257 258 long startGarbageCollectionMs = System.currentTimeMillis(); 259 260 // Remove all files in the index which aren't referenced in the workspace 261 int gcFiles = cleanGarbage(currentTimeMs, locations, subMonitor.split(1)); 262 263 long startFingerprintTestMs = System.currentTimeMillis(); 264 265 Map<IPath, FingerprintTestResult> fingerprints = testFingerprints(locations, subMonitor.split(1)); 266 Set<IPath> indexablesWithChanges = new HashSet<>( 267 getIndexablesThatHaveChanged(locations, fingerprints)); 268 269 // Compute the total number of bytes to be read in and indexed 270 long startIndexingMs = System.currentTimeMillis(); 271 long totalSizeToIndex = 0; 272 for (IPath next : indexablesWithChanges) { 273 FingerprintTestResult nextFingerprint = fingerprints.get(next); 274 totalSizeToIndex += nextFingerprint.getNewFingerprint().getSize(); 275 } 276 double tickCoefficient = totalSizeToIndex == 0 ? 0.0 277 : (double) TOTAL_TICKS_TO_REPORT_DURING_INDEXING / (double) totalSizeToIndex; 278 279 int classesIndexed = 0; 280 SubMonitor loopMonitor = subMonitor.split(94).setWorkRemaining(TOTAL_TICKS_TO_REPORT_DURING_INDEXING); 281 for (IPath next : indexablesWithChanges) { 282 FingerprintTestResult nextFingerprint = fingerprints.get(next); 283 int ticks = (int) (nextFingerprint.getNewFingerprint().getSize() * tickCoefficient); 284 285 classesIndexed += rescanArchive(currentTimeMs, next, snapshot.get(next), 286 fingerprints.get(next).getNewFingerprint(), loopMonitor.split(ticks)); 287 } 288 289 long endIndexingMs = System.currentTimeMillis(); 290 291 Map<IPath, List<IJavaElement>> pathsToUpdate = new HashMap<>(); 292 293 for (IPath next : locations) { 294 if (!indexablesWithChanges.contains(next)) { 295 pathsToUpdate.put(next, snapshot.get(next)); 296 continue; 297 } 298 } 299 300 updateResourceMappings(pathsToUpdate, subMonitor.split(1)); 301 302 // Flush the database to disk 303 this.nd.acquireWriteLock(subMonitor.split(1)); 304 try { 305 this.nd.getDB().flush(); 306 } finally { 307 this.nd.releaseWriteLock(); 308 } 309 310 fireDelta(indexablesWithChanges, subMonitor.split(1)); 311 312 if (DEBUG) { 313 Package.logInfo("Rescan finished"); //$NON-NLS-1$ 314 } 315 316 long endResourceMappingMs = System.currentTimeMillis(); 317 318 long locateIndexablesTimeMs = startGarbageCollectionMs - currentTimeMs; 319 long garbageCollectionMs = startFingerprintTestMs - startGarbageCollectionMs; 320 long fingerprintTimeMs = startIndexingMs - startFingerprintTestMs; 321 long indexingTimeMs = endIndexingMs - startIndexingMs; 322 long resourceMappingTimeMs = endResourceMappingMs - endIndexingMs; 323 324 double averageGcTimeMs = gcFiles == 0 ? 0 : (double) garbageCollectionMs / (double) gcFiles; 325 double averageIndexTimeMs = classesIndexed == 0 ? 0 : (double) indexingTimeMs / (double) classesIndexed; 326 double averageFingerprintTimeMs = locations.size() == 0 ? 0 327 : (double) fingerprintTimeMs / (double) locations.size(); 328 double averageResourceMappingMs = pathsToUpdate.size() == 0 ? 0 329 : (double) resourceMappingTimeMs / (double) pathsToUpdate.size(); 330 331 if (DEBUG_TIMING) { 332 DecimalFormat msFormat = new DecimalFormat("#0.###"); //$NON-NLS-1$ 333 DecimalFormat percentFormat = new DecimalFormat("#0.###"); //$NON-NLS-1$ 334 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS\n"); //$NON-NLS-1$ 335 System.out.println("Indexing done at " + format.format(new Date(endResourceMappingMs)) //$NON-NLS-1$ 336 + " Located " + locations.size() + " indexables in " + locateIndexablesTimeMs + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 337 if (gcFiles != 0) { 338 System.out.println(" Collected garbage from " + gcFiles + " files in " + garbageCollectionMs //$NON-NLS-1$//$NON-NLS-2$ 339 + "ms, average time = " + msFormat.format(averageGcTimeMs) + "ms"); //$NON-NLS-1$//$NON-NLS-2$ 340 } 341 System.out.println(" Tested " + locations.size() + " fingerprints in " + fingerprintTimeMs //$NON-NLS-1$ //$NON-NLS-2$ 342 + "ms, average time = " + msFormat.format(averageFingerprintTimeMs) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ 343 if (classesIndexed != 0) { 344 System.out.println(" Indexed " + classesIndexed + " classes (from " + indexablesWithChanges.size() //$NON-NLS-1$//$NON-NLS-2$ 345 + " files containing " + Database.formatByteString(totalSizeToIndex) + ") in " + indexingTimeMs //$NON-NLS-1$ //$NON-NLS-2$ 346 + "ms, average time per class = " + msFormat.format(averageIndexTimeMs) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ 347 } 348 if (pathsToUpdate.size() != 0) { 349 System.out.println(" Updated " + pathsToUpdate.size() + " paths in " + resourceMappingTimeMs //$NON-NLS-1$//$NON-NLS-2$ 350 + "ms, average time = " + msFormat.format(averageResourceMappingMs) + "ms"); //$NON-NLS-1$//$NON-NLS-2$ 351 } 352 System.out.println(" " + db.getChunkStats()); //$NON-NLS-1$ 353 long cacheHits = db.getCacheHits(); 354 long cacheMisses = db.getCacheMisses(); 355 long totalReads = cacheMisses + cacheHits; 356 double cacheMissPercent = totalReads == 0 ? 0 : (cacheMisses * 100.0) / totalReads; 357 System.out.println(" Cache misses = " + cacheMisses + " (" //$NON-NLS-1$//$NON-NLS-2$ 358 + percentFormat.format(cacheMissPercent) + "%)"); //$NON-NLS-1$ 359 360 long bytesRead = db.getBytesRead(); 361 long bytesWritten = db.getBytesWritten(); 362 double totalTimeMs = endResourceMappingMs - currentTimeMs; 363 long flushTimeMs = db.getCumulativeFlushTimeMs(); 364 double flushPercent = totalTimeMs == 0 ? 0 : flushTimeMs * 100.0 / totalTimeMs; 365 System.out.println(" Reads = " + Database.formatByteString(bytesRead) + ", writes = " + Database.formatByteString(bytesWritten)); //$NON-NLS-1$//$NON-NLS-2$ 366 double averageReadBytesPerSecond = db.getAverageReadBytesPerMs() * 1000; 367 double averageWriteBytesPerSecond = db.getAverageWriteBytesPerMs() * 1000; 368 if (bytesRead > Database.CHUNK_SIZE * 100) { 369 System.out.println( 370 " Read speed = " + Database.formatByteString((long) averageReadBytesPerSecond) + "/s"); //$NON-NLS-1$//$NON-NLS-2$ 371 } 372 if (bytesWritten > Database.CHUNK_SIZE * 100) { 373 System.out.println( 374 " Write speed = " + Database.formatByteString((long) averageWriteBytesPerSecond) + "/s"); //$NON-NLS-1$ //$NON-NLS-2$ 375 } 376 377 System.out.println(" Time spent performing flushes = " //$NON-NLS-1$ 378 + msFormat.format(flushTimeMs) + "ms (" //$NON-NLS-1$ 379 + percentFormat.format(flushPercent) + "%)"); //$NON-NLS-1$ 380 System.out.println(" Total indexing time = " + msFormat.format(totalTimeMs) + "ms"); //$NON-NLS-1$//$NON-NLS-2$ 381 } 382 383 if (DEBUG_ALLOCATIONS) { 384 try (IReader readLock = this.nd.acquireReadLock()) { 385 this.nd.getDB().reportFreeBlocks(); 386 this.nd.getDB().getMemoryStats().printMemoryStats(this.nd.getTypeRegistry()); 387 } 388 } 389 } 390 fireDelta(Set<IPath> indexablesWithChanges, IProgressMonitor monitor)391 private void fireDelta(Set<IPath> indexablesWithChanges, IProgressMonitor monitor) { 392 SubMonitor subMonitor = SubMonitor.convert(monitor, 1); 393 IProject[] projects = this.root.getProjects(); 394 395 List<IProject> projectsToScan = new ArrayList<>(); 396 397 for (IProject next : projects) { 398 if (next.isOpen()) { 399 projectsToScan.add(next); 400 } 401 } 402 JavaModel model = JavaModelManager.getJavaModelManager().getJavaModel(); 403 boolean hasChanges = false; 404 JavaElementDelta delta = new JavaElementDelta(model); 405 SubMonitor projectLoopMonitor = subMonitor.split(1).setWorkRemaining(projectsToScan.size()); 406 for (IProject project : projectsToScan) { 407 projectLoopMonitor.split(1); 408 try { 409 if (project.isOpen() && project.isNatureEnabled(JavaCore.NATURE_ID)) { 410 IJavaProject javaProject = JavaCore.create(project); 411 412 IPackageFragmentRoot[] roots = javaProject.getAllPackageFragmentRoots(); 413 414 for (IPackageFragmentRoot next : roots) { 415 if (next.isArchive()) { 416 IPath location = JavaIndex.getLocationForElement(next); 417 418 if (indexablesWithChanges.contains(location)) { 419 hasChanges = true; 420 delta.changed(next, 421 IJavaElementDelta.F_CONTENT | IJavaElementDelta.F_ARCHIVE_CONTENT_CHANGED); 422 } 423 } 424 } 425 } 426 } catch (CoreException e) { 427 Package.log(e); 428 } 429 } 430 431 if (hasChanges) { 432 fireChange(IndexerEvent.createChange(delta)); 433 } 434 } 435 updateResourceMappings(Map<IPath, List<IJavaElement>> pathsToUpdate, IProgressMonitor monitor)436 private void updateResourceMappings(Map<IPath, List<IJavaElement>> pathsToUpdate, IProgressMonitor monitor) { 437 SubMonitor subMonitor = SubMonitor.convert(monitor, pathsToUpdate.keySet().size()); 438 439 JavaIndex index = JavaIndex.getIndex(this.nd); 440 441 for (Entry<IPath, List<IJavaElement>> entry : pathsToUpdate.entrySet()) { 442 SubMonitor iterationMonitor = subMonitor.split(1).setWorkRemaining(10); 443 444 this.nd.acquireWriteLock(iterationMonitor.split(1)); 445 try { 446 NdResourceFile resourceFile = index.getResourceFile(entry.getKey().toString().toCharArray()); 447 if (resourceFile == null) { 448 continue; 449 } 450 451 attachWorkspaceFilesToResource(entry.getValue(), resourceFile); 452 } finally { 453 this.nd.releaseWriteLock(); 454 } 455 456 } 457 } 458 459 /** 460 * Clean up unneeded files here, but only do so if it's been a long time since the file was last referenced. Being 461 * too eager about removing old files means that operations which temporarily cause a file to become unreferenced 462 * will run really slowly. also eagerly clean up any partially-indexed files we discover during the scan. That is, 463 * if we discover a file with a timestamp of 0, it indicates that the indexer or all of Eclipse crashed midway 464 * through indexing the file. Such garbage should be cleaned up as soon as possible, since it will never be useful. 465 * 466 * @param currentTimeMillis timestamp of the time at which the indexing operation started 467 * @param allIndexables list of all referenced java roots 468 * @param monitor progress monitor 469 * @return the number of indexables in the index, prior to garbage collection 470 */ cleanGarbage(long currentTimeMillis, Collection<IPath> allIndexables, IProgressMonitor monitor)471 private int cleanGarbage(long currentTimeMillis, Collection<IPath> allIndexables, IProgressMonitor monitor) { 472 JavaIndex index = JavaIndex.getIndex(this.nd); 473 474 int result = 0; 475 HashSet<IPath> paths = new HashSet<>(); 476 paths.addAll(allIndexables); 477 SubMonitor subMonitor = SubMonitor.convert(monitor, 3); 478 479 List<NdResourceFile> garbage = new ArrayList<>(); 480 List<NdResourceFile> needsUpdate = new ArrayList<>(); 481 482 long usageTimestampUpdatePeriod = getUsageTimestampUpdatePeriod(); 483 long garbageCleanupTimeout = getGarbageCleanupTimeout(); 484 // Build up the list of NdResourceFiles that either need to be garbage collected or 485 // have their read timestamps updated. 486 try (IReader reader = this.nd.acquireReadLock()) { 487 List<NdResourceFile> resourceFiles = index.getAllResourceFiles(); 488 489 result = resourceFiles.size(); 490 SubMonitor testMonitor = subMonitor.split(1).setWorkRemaining(resourceFiles.size()); 491 for (NdResourceFile next : resourceFiles) { 492 testMonitor.split(1); 493 if (!next.isDoneIndexing()) { 494 garbage.add(next); 495 } else { 496 IPath nextPath = new Path(next.getLocation().toString()); 497 long timeLastUsed = next.getTimeLastUsed(); 498 long timeSinceLastUsed = currentTimeMillis - timeLastUsed; 499 500 if (paths.contains(nextPath)) { 501 if (timeSinceLastUsed > usageTimestampUpdatePeriod) { 502 needsUpdate.add(next); 503 } 504 } else { 505 if (timeSinceLastUsed > garbageCleanupTimeout) { 506 garbage.add(next); 507 } 508 } 509 } 510 } 511 } 512 513 SubMonitor deleteMonitor = subMonitor.split(1).setWorkRemaining(garbage.size()); 514 for (NdResourceFile next : garbage) { 515 deleteResource(next, deleteMonitor.split(1)); 516 } 517 518 SubMonitor updateMonitor = subMonitor.split(1).setWorkRemaining(needsUpdate.size()); 519 for (NdResourceFile next : needsUpdate) { 520 this.nd.acquireWriteLock(updateMonitor.split(1)); 521 try { 522 if (next.isInIndex()) { 523 next.setTimeLastUsed(currentTimeMillis); 524 } 525 } finally { 526 this.nd.releaseWriteLock(); 527 } 528 } 529 530 return result; 531 } 532 533 /** 534 * Performs a non-atomic delete of the given resource file. First, it marks the file as being invalid 535 * (by clearing out its timestamp). Then it deletes the children of the resource file, one child at a time. 536 * Once all the children are deleted, the resource itself is deleted. The result on the database is exactly 537 * the same as if the caller had called toDelete.delete(), but doing it this way ensures that a write lock 538 * will never be held for a nontrivial amount of time. 539 */ deleteResource(NdResourceFile toDelete, IProgressMonitor monitor)540 protected void deleteResource(NdResourceFile toDelete, IProgressMonitor monitor) { 541 SubMonitor deletionMonitor = SubMonitor.convert(monitor, 10); 542 543 this.nd.acquireWriteLock(deletionMonitor.split(1)); 544 try { 545 if (toDelete.isInIndex()) { 546 toDelete.markAsInvalid(); 547 } 548 } finally { 549 this.nd.releaseWriteLock(); 550 } 551 552 for (;;) { 553 this.nd.acquireWriteLock(deletionMonitor.split(1)); 554 try { 555 if (!toDelete.isInIndex()) { 556 break; 557 } 558 559 int numChildren = toDelete.getTypeCount(); 560 deletionMonitor.setWorkRemaining(numChildren + 1); 561 if (numChildren == 0) { 562 break; 563 } 564 565 NdType nextDeletion = toDelete.getType(numChildren - 1); 566 if (DEBUG_INSERTIONS) { 567 Package.logInfo("Deleting " + nextDeletion.getTypeId().getFieldDescriptor().getString() + " from " //$NON-NLS-1$//$NON-NLS-2$ 568 + toDelete.getLocation().getString() + " " + toDelete.address); //$NON-NLS-1$ 569 } 570 nextDeletion.delete(); 571 } finally { 572 this.nd.releaseWriteLock(); 573 } 574 } 575 576 this.nd.acquireWriteLock(deletionMonitor.split(1)); 577 try { 578 if (toDelete.isInIndex()) { 579 toDelete.delete(); 580 } 581 } finally { 582 this.nd.releaseWriteLock(); 583 } 584 } 585 testFingerprints(Collection<IPath> allIndexables, IProgressMonitor monitor)586 private Map<IPath, FingerprintTestResult> testFingerprints(Collection<IPath> allIndexables, 587 IProgressMonitor monitor) throws CoreException { 588 SubMonitor subMonitor = SubMonitor.convert(monitor, allIndexables.size()); 589 Map<IPath, FingerprintTestResult> result = new HashMap<>(); 590 591 for (IPath next : allIndexables) { 592 result.put(next, testForChanges(next, subMonitor.split(1))); 593 } 594 595 return result; 596 } 597 598 /** 599 * Rescans an archive (a jar, zip, or class file on the filesystem). Returns the number of classes indexed. 600 * @throws JavaModelException 601 */ rescanArchive(long currentTimeMillis, IPath thePath, List<IJavaElement> elementsMappingOntoLocation, FileFingerprint fingerprint, IProgressMonitor monitor)602 private int rescanArchive(long currentTimeMillis, IPath thePath, List<IJavaElement> elementsMappingOntoLocation, 603 FileFingerprint fingerprint, IProgressMonitor monitor) throws JavaModelException { 604 SubMonitor subMonitor = SubMonitor.convert(monitor, 100); 605 if (elementsMappingOntoLocation.isEmpty()) { 606 return 0; 607 } 608 609 IJavaElement element = elementsMappingOntoLocation.get(0); 610 611 String pathString = thePath.toString(); 612 JavaIndex javaIndex = JavaIndex.getIndex(this.nd); 613 614 NdResourceFile resourceFile; 615 616 this.nd.acquireWriteLock(subMonitor.split(5)); 617 try { 618 resourceFile = new NdResourceFile(this.nd); 619 resourceFile.setTimeLastUsed(currentTimeMillis); 620 resourceFile.setLocation(pathString); 621 IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) element 622 .getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); 623 IPath rootPathString = JavaIndex.getLocationForElement(packageFragmentRoot); 624 if (!rootPathString.equals(thePath)) { 625 resourceFile.setPackageFragmentRoot(rootPathString.toString().toCharArray()); 626 } 627 attachWorkspaceFilesToResource(elementsMappingOntoLocation, resourceFile); 628 } finally { 629 this.nd.releaseWriteLock(); 630 } 631 632 if (DEBUG) { 633 Package.logInfo("rescanning " + thePath.toString() + ", " + fingerprint); //$NON-NLS-1$ //$NON-NLS-2$ 634 } 635 int result = 0; 636 try { 637 if (fingerprint.fileExists()) { 638 result = addElement(resourceFile, element, subMonitor.split(50)); 639 } 640 } catch (JavaModelException e) { 641 if (DEBUG) { 642 Package.log("the file " + pathString + " cannot be indexed due to a recoverable error", null); //$NON-NLS-1$ //$NON-NLS-2$ 643 } 644 // If this file can't be indexed due to a recoverable error, delete the NdResourceFile entry for it. 645 this.nd.acquireWriteLock(subMonitor.split(5)); 646 try { 647 if (resourceFile.isInIndex()) { 648 resourceFile.delete(); 649 } 650 } finally { 651 this.nd.releaseWriteLock(); 652 } 653 return 0; 654 } catch (RuntimeException e) { 655 if (DEBUG) { 656 Package.log("A RuntimeException occurred while indexing " + pathString, e); //$NON-NLS-1$ 657 } 658 throw e; 659 } catch (FileNotFoundException e) { 660 fingerprint = FileFingerprint.getEmpty(); 661 } 662 663 if (DEBUG && !fingerprint.fileExists()) { 664 Package.log("the file " + pathString + " was not indexed because it does not exist", null); //$NON-NLS-1$ //$NON-NLS-2$ 665 } 666 667 List<NdResourceFile> allResourcesWithThisPath = Collections.emptyList(); 668 // Now update the timestamp and delete all older versions of this resource that exist in the index 669 this.nd.acquireWriteLock(subMonitor.split(1)); 670 try { 671 if (resourceFile.isInIndex()) { 672 resourceFile.setFingerprint(fingerprint); 673 allResourcesWithThisPath = javaIndex.findResourcesWithPath(pathString); 674 // Remove this file from the file state cache, since the act of indexing it may have changed its 675 // up-to-date status. Note that it isn't necessarily up-to-date now -- it may have changed again 676 // while we were indexing it. 677 this.fileStateCache.remove(resourceFile.getLocation().getString()); 678 } 679 } finally { 680 this.nd.releaseWriteLock(); 681 } 682 683 SubMonitor deletionMonitor = subMonitor.split(40).setWorkRemaining(allResourcesWithThisPath.size() - 1); 684 for (NdResourceFile next : allResourcesWithThisPath) { 685 if (!next.equals(resourceFile)) { 686 deleteResource(next, deletionMonitor.split(1)); 687 } 688 } 689 690 return result; 691 } 692 attachWorkspaceFilesToResource(List<IJavaElement> elementsMappingOntoLocation, NdResourceFile resourceFile)693 private void attachWorkspaceFilesToResource(List<IJavaElement> elementsMappingOntoLocation, 694 NdResourceFile resourceFile) { 695 for (IJavaElement next : elementsMappingOntoLocation) { 696 IResource nextResource = next.getResource(); 697 if (nextResource != null) { 698 new NdWorkspaceLocation(this.nd, resourceFile, 699 nextResource.getFullPath().toString().toCharArray()); 700 } 701 } 702 } 703 704 /** 705 * Adds an archive to the index, under the given NdResourceFile. 706 * @throws FileNotFoundException if the file does not exist 707 */ addElement(NdResourceFile resourceFile, IJavaElement element, IProgressMonitor monitor)708 private int addElement(NdResourceFile resourceFile, IJavaElement element, IProgressMonitor monitor) 709 throws JavaModelException, FileNotFoundException { 710 SubMonitor subMonitor = SubMonitor.convert(monitor); 711 712 if (element instanceof JarPackageFragmentRoot) { 713 JarPackageFragmentRoot jarRoot = (JarPackageFragmentRoot) element; 714 715 IPath workspacePath = jarRoot.getPath(); 716 IPath location = JavaIndex.getLocationForElement(jarRoot); 717 718 int classesIndexed = 0; 719 try (ZipFile zipFile = new ZipFile(JavaModelManager.getLocalFile(jarRoot.getPath()))) { 720 // Used for the error-handling unit tests 721 if (JavaModelManager.throwIoExceptionsInGetZipFile) { 722 if (DEBUG) { 723 Package.logInfo("Throwing simulated IOException for error handling test case"); //$NON-NLS-1$ 724 } 725 throw new IOException(); 726 } 727 subMonitor.setWorkRemaining(zipFile.size()); 728 729 // Preallocate memory for the zipfile entries 730 this.nd.acquireWriteLock(subMonitor.split(5)); 731 try { 732 resourceFile.allocateZipEntries(zipFile.size()); 733 } finally { 734 this.nd.releaseWriteLock(); 735 } 736 for (Enumeration<? extends ZipEntry> e = zipFile.entries(); e.hasMoreElements();) { 737 SubMonitor nextEntry = subMonitor.split(1).setWorkRemaining(2); 738 ZipEntry member = e.nextElement(); 739 String fileName = member.getName(); 740 boolean classFileName = org.eclipse.jdt.internal.compiler.util.Util.isClassFileName(fileName); 741 if (member.isDirectory() || !classFileName) { 742 this.nd.acquireWriteLock(subMonitor.split(5)); 743 try { 744 if (resourceFile.isInIndex()) { 745 if (DEBUG_INSERTIONS) { 746 Package.logInfo("Inserting non-class file " + fileName + " into " //$NON-NLS-1$//$NON-NLS-2$ 747 + resourceFile.getLocation().getString() + " " + resourceFile.address); //$NON-NLS-1$ 748 } 749 resourceFile.addZipEntry(fileName); 750 751 if (fileName.equals(TypeConstants.META_INF_MANIFEST_MF)) { 752 try (InputStream inputStream = zipFile.getInputStream(member)) { 753 char[] chars = getInputStreamAsCharArray(inputStream, -1, UTF_8); 754 755 resourceFile.setManifestContent(chars); 756 } 757 } 758 } 759 } finally { 760 this.nd.releaseWriteLock(); 761 } 762 } 763 if (member.isDirectory()) { 764 // Note that non-empty directories are stored implicitly (as the parent directory of a file 765 // or class within the jar). Empty directories are not currently stored in the index. 766 continue; 767 } 768 nextEntry.split(1); 769 770 if (classFileName) { 771 String binaryName = fileName.substring(0, 772 fileName.length() - SuffixConstants.SUFFIX_STRING_class.length()); 773 char[] fieldDescriptor = JavaNames.binaryNameToFieldDescriptor(binaryName.toCharArray()); 774 String indexPath = jarRoot.getHandleIdentifier() + IDependent.JAR_FILE_ENTRY_SEPARATOR 775 + binaryName; 776 BinaryTypeDescriptor descriptor = new BinaryTypeDescriptor(location.toString().toCharArray(), 777 fieldDescriptor, workspacePath.toString().toCharArray(), indexPath.toCharArray()); 778 try { 779 byte[] contents = org.eclipse.jdt.internal.compiler.util.Util.getZipEntryByteContent(member, 780 zipFile); 781 ClassFileReader classFileReader = new ClassFileReader(contents, descriptor.indexPath, true); 782 if (addClassToIndex(resourceFile, descriptor.fieldDescriptor, descriptor.indexPath, 783 classFileReader, nextEntry.split(1))) { 784 classesIndexed++; 785 } 786 } catch (CoreException | ClassFormatException exception) { 787 Package.log("Unable to index " + descriptor.toString(), exception); //$NON-NLS-1$ 788 } 789 } 790 } 791 } catch (ZipException e) { 792 Package.log("The zip file " + jarRoot.getPath() + " was corrupt", e); //$NON-NLS-1$//$NON-NLS-2$ 793 // Indicates a corrupt zip file. Treat this like an empty zip file. 794 this.nd.acquireWriteLock(null); 795 try { 796 if (resourceFile.isInIndex()) { 797 resourceFile.setFlags(NdResourceFile.FLG_CORRUPT_ZIP_FILE); 798 } 799 } finally { 800 this.nd.releaseWriteLock(); 801 } 802 } catch (FileNotFoundException e) { 803 throw e; 804 } catch (IOException ioException) { 805 throw new JavaModelException(ioException, IJavaModelStatusConstants.IO_EXCEPTION); 806 } catch (CoreException coreException) { 807 throw new JavaModelException(coreException); 808 } 809 810 if (DEBUG && classesIndexed == 0) { 811 Package.logInfo("The path " + element.getPath() + " contained no class files"); //$NON-NLS-1$ //$NON-NLS-2$ 812 } 813 return classesIndexed; 814 } else if (element instanceof IOrdinaryClassFile) { 815 IOrdinaryClassFile classFile = (IOrdinaryClassFile) element; 816 817 SubMonitor iterationMonitor = subMonitor.split(1); 818 BinaryTypeDescriptor descriptor = BinaryTypeFactory.createDescriptor(classFile); 819 820 boolean indexed = false; 821 try { 822 ClassFileReader classFileReader = BinaryTypeFactory.rawReadTypeTestForExists(descriptor, true, false); 823 if (classFileReader != null) { 824 indexed = addClassToIndex(resourceFile, descriptor.fieldDescriptor, descriptor.indexPath, 825 classFileReader, iterationMonitor); 826 } 827 } catch (CoreException | ClassFormatException e) { 828 Package.log("Unable to index " + classFile.toString(), e); //$NON-NLS-1$ 829 } 830 831 return indexed ? 1 : 0; 832 } else { 833 Package.logInfo("Unable to index elements of type " + element); //$NON-NLS-1$ 834 return 0; 835 } 836 } 837 addClassToIndex(NdResourceFile resourceFile, char[] fieldDescriptor, char[] indexPath, ClassFileReader binaryType, IProgressMonitor monitor)838 private boolean addClassToIndex(NdResourceFile resourceFile, char[] fieldDescriptor, char[] indexPath, 839 ClassFileReader binaryType, IProgressMonitor monitor) throws ClassFormatException, CoreException { 840 SubMonitor subMonitor = SubMonitor.convert(monitor, 100); 841 ClassFileToIndexConverter converter = new ClassFileToIndexConverter(resourceFile); 842 843 boolean indexed = false; 844 this.nd.acquireWriteLock(subMonitor.split(5)); 845 try { 846 if (resourceFile.isInIndex()) { 847 if (DEBUG_INSERTIONS) { 848 Package.logInfo("Inserting " + new String(fieldDescriptor) + " into " //$NON-NLS-1$//$NON-NLS-2$ 849 + resourceFile.getLocation().getString() + " " + resourceFile.address); //$NON-NLS-1$ 850 } 851 converter.addType(binaryType, fieldDescriptor, subMonitor.split(45)); 852 resourceFile.setJdkLevel(binaryType.getVersion()); 853 indexed = true; 854 } 855 } finally { 856 this.nd.releaseWriteLock(); 857 } 858 859 if (DEBUG_SELFTEST && indexed) { 860 // When this debug flag is on, we test everything written to the index by reading it back immediately after 861 // indexing and comparing it with the original class file. 862 JavaIndex index = JavaIndex.getIndex(this.nd); 863 try (IReader readLock = this.nd.acquireReadLock()) { 864 NdTypeId typeId = index.findType(fieldDescriptor); 865 NdType targetType = null; 866 if (typeId != null) { 867 List<NdType> implementations = typeId.getTypes(); 868 for (NdType nextType : implementations) { 869 NdResourceFile nextResourceFile = nextType.getResourceFile(); 870 if (nextResourceFile.equals(resourceFile)) { 871 targetType = nextType; 872 break; 873 } 874 } 875 } 876 877 if (targetType != null) { 878 IndexBinaryType actualType = new IndexBinaryType(TypeRef.create(targetType), indexPath); 879 IndexTester.testType(binaryType, actualType); 880 } else { 881 Package.logInfo( 882 "Could not find class in index immediately after indexing it: " + new String(indexPath)); //$NON-NLS-1$ 883 } 884 } catch (RuntimeException e) { 885 Package.log("Error during indexing: " + new String(indexPath), e); //$NON-NLS-1$ 886 } 887 } 888 return indexed; 889 } 890 891 /** 892 * Given a list of fragment roots, returns the subset of roots that have changed since the last time they were 893 * indexed. 894 */ getIndexablesThatHaveChanged(Collection<IPath> indexables, Map<IPath, FingerprintTestResult> fingerprints)895 private List<IPath> getIndexablesThatHaveChanged(Collection<IPath> indexables, 896 Map<IPath, FingerprintTestResult> fingerprints) { 897 List<IPath> indexablesWithChanges = new ArrayList<>(); 898 for (IPath next : indexables) { 899 FingerprintTestResult testResult = fingerprints.get(next); 900 901 if (!testResult.matches()) { 902 indexablesWithChanges.add(next); 903 } 904 } 905 return indexablesWithChanges; 906 } 907 testForChanges(IPath thePath, IProgressMonitor monitor)908 private FingerprintTestResult testForChanges(IPath thePath, IProgressMonitor monitor) throws CoreException { 909 SubMonitor subMonitor = SubMonitor.convert(monitor, 100); 910 JavaIndex javaIndex = JavaIndex.getIndex(this.nd); 911 String pathString = thePath.toString(); 912 913 subMonitor.split(50); 914 NdResourceFile resourceFile = null; 915 FileFingerprint fingerprint = FileFingerprint.getEmpty(); 916 this.nd.acquireReadLock(); 917 try { 918 resourceFile = javaIndex.getResourceFile(pathString.toCharArray()); 919 920 if (resourceFile != null) { 921 fingerprint = resourceFile.getFingerprint(); 922 } 923 } finally { 924 this.nd.releaseReadLock(); 925 } 926 927 FingerprintTestResult result = fingerprint.test(thePath, subMonitor.split(40)); 928 929 // If this file hasn't changed but its timestamp has, write an updated fingerprint to the database 930 if (resourceFile != null && result.matches() && result.needsNewFingerprint()) { 931 this.nd.acquireWriteLock(subMonitor.split(10)); 932 try { 933 if (resourceFile.isInIndex()) { 934 if (DEBUG) { 935 Package.logInfo( 936 "Writing updated fingerprint for " + thePath + ": " + result.getNewFingerprint()); //$NON-NLS-1$//$NON-NLS-2$ 937 } 938 resourceFile.setFingerprint(result.getNewFingerprint()); 939 } 940 } finally { 941 this.nd.releaseWriteLock(); 942 } 943 } 944 945 return result; 946 } 947 Indexer(Nd toPopulate, IWorkspaceRoot workspaceRoot)948 public Indexer(Nd toPopulate, IWorkspaceRoot workspaceRoot) { 949 this.nd = toPopulate; 950 this.root = workspaceRoot; 951 this.rescanJob.setSystem(true); 952 this.rescanJob.setJobGroup(this.group); 953 this.rebuildIndexJob.setSystem(true); 954 this.rebuildIndexJob.setJobGroup(this.group); 955 this.fileStateCache = FileStateCache.getCache(toPopulate); 956 } 957 rescanAll()958 public void rescanAll() { 959 if (DEBUG_SCHEDULING) { 960 Package.logInfo("Scheduling rescanAll now"); //$NON-NLS-1$ 961 } 962 synchronized (this.automaticIndexingMutex) { 963 if (!this.enableAutomaticIndexing) { 964 if (!this.indexerDirtiedWhileDisabled) { 965 this.indexerDirtiedWhileDisabled = true; 966 } 967 return; 968 } 969 } 970 if (!JavaIndex.isEnabled()) { 971 return; 972 } 973 this.rescanJob.schedule(); 974 } 975 976 /** 977 * Adds the given listener. It will be notified when Nd changes. No strong references 978 * will be retained to the listener. 979 */ addListener(Listener newListener)980 public void addListener(Listener newListener) { 981 synchronized (this.listenersMutex) { 982 Set<Listener> oldListeners = this.listeners; 983 this.listeners = Collections.newSetFromMap(new WeakHashMap<>()); 984 this.listeners.addAll(oldListeners); 985 this.listeners.add(newListener); 986 } 987 } 988 removeListener(Listener oldListener)989 public void removeListener(Listener oldListener) { 990 synchronized (this.listenersMutex) { 991 if (!this.listeners.contains(oldListener)) { 992 return; 993 } 994 Set<Listener> oldListeners = this.listeners; 995 this.listeners = Collections.newSetFromMap(new WeakHashMap<>()); 996 this.listeners.addAll(oldListeners); 997 this.listeners.remove(oldListener); 998 } 999 } 1000 fireChange(IndexerEvent event)1001 private void fireChange(IndexerEvent event) { 1002 Set<Listener> localListeners; 1003 synchronized (this.listenersMutex) { 1004 localListeners = this.listeners; 1005 } 1006 1007 for (Listener next : localListeners) { 1008 next.consume(event); 1009 } 1010 } 1011 waitForIndex(IProgressMonitor monitor)1012 public void waitForIndex(IProgressMonitor monitor) { 1013 try { 1014 boolean shouldRescan = false; 1015 synchronized (this.automaticIndexingMutex) { 1016 if (!this.enableAutomaticIndexing && this.indexerDirtiedWhileDisabled) { 1017 shouldRescan = true; 1018 } 1019 } 1020 if (shouldRescan) { 1021 this.rescanJob.schedule(); 1022 } 1023 this.rescanJob.join(0, monitor); 1024 } catch (InterruptedException e) { 1025 throw new OperationCanceledException(); 1026 } 1027 } 1028 waitForIndex(int waitingPolicy, IProgressMonitor monitor)1029 public void waitForIndex(int waitingPolicy, IProgressMonitor monitor) { 1030 if (!JavaIndex.isEnabled()) { 1031 return; 1032 } 1033 switch (waitingPolicy) { 1034 case IJob.ForceImmediate: { 1035 break; 1036 } 1037 case IJob.CancelIfNotReady: { 1038 if (this.rescanJob.getState() != Job.NONE) { 1039 throw new OperationCanceledException(); 1040 } 1041 break; 1042 } 1043 case IJob.WaitUntilReady: { 1044 waitForIndex(monitor); 1045 break; 1046 } 1047 } 1048 } 1049 rebuildIndex(IProgressMonitor monitor)1050 public void rebuildIndex(IProgressMonitor monitor) throws CoreException { 1051 SubMonitor subMonitor = SubMonitor.convert(monitor, 100); 1052 1053 this.rescanJob.cancel(); 1054 try { 1055 this.rescanJob.join(0, subMonitor.split(1)); 1056 } catch (InterruptedException e) { 1057 // Nothing to do. 1058 } 1059 this.nd.acquireWriteLock(subMonitor.split(1)); 1060 try { 1061 this.nd.clear(subMonitor.split(2)); 1062 this.nd.getDB().flush(); 1063 } finally { 1064 this.nd.releaseWriteLock(); 1065 } 1066 if (!JavaIndex.isEnabled()) { 1067 return; 1068 } 1069 rescan(subMonitor.split(97)); 1070 } 1071 requestRebuildIndex()1072 public void requestRebuildIndex() { 1073 this.rebuildIndexJob.schedule(); 1074 } 1075 1076 /** 1077 * Dirties the given filesystem location. This must point to a single file (not a folder) that needs to be 1078 * rescanned. The file may have been added, removed, or changed. 1079 * 1080 * @param location an absolute filesystem location 1081 */ makeDirty(IPath location)1082 public void makeDirty(IPath location) { 1083 this.fileStateCache.remove(location.toString()); 1084 rescanAll(); 1085 } 1086 1087 /** 1088 * Schedules a rescan of the given project. 1089 */ makeDirty(IProject project)1090 public void makeDirty(IProject project) { 1091 this.fileStateCache.clear(); 1092 rescanAll(); 1093 } 1094 1095 /** 1096 * Schedules a rescan of the given path (which may be either a workspace path or an absolute path on the local 1097 * filesystem). This may point to either a single file or a folder that needs to be rescanned. Any resource that 1098 * has this path as a prefix will be rescanned. 1099 * 1100 * @param pathToRescan 1101 */ makeWorkspacePathDirty(IPath pathToRescan)1102 public void makeWorkspacePathDirty(IPath pathToRescan) { 1103 this.fileStateCache.clear(); 1104 rescanAll(); 1105 } 1106 } 1107