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