/******************************************************************************* * Copyright (c) 2000, 2010 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.core.search.indexing; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Locale; import java.util.Map; import java.util.zip.CRC32; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.search.IJavaSearchScope; import org.eclipse.jdt.core.search.SearchDocument; import org.eclipse.jdt.core.search.SearchEngine; import org.eclipse.jdt.core.search.SearchParticipant; import org.eclipse.jdt.internal.compiler.ISourceElementRequestor; import org.eclipse.jdt.internal.compiler.SourceElementParser; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable; import org.eclipse.jdt.internal.compiler.util.SimpleSet; import org.eclipse.jdt.internal.core.JavaModel; import org.eclipse.jdt.internal.core.JavaModelManager; import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.core.index.DiskIndex; import org.eclipse.jdt.internal.core.index.Index; import org.eclipse.jdt.internal.core.search.BasicSearchEngine; import org.eclipse.jdt.internal.core.search.PatternSearchJob; import org.eclipse.jdt.internal.core.search.processing.IJob; import org.eclipse.jdt.internal.core.search.processing.JobManager; import org.eclipse.jdt.internal.core.util.Messages; import org.eclipse.jdt.internal.core.util.Util; public class IndexManager extends JobManager implements IIndexConstants { // key = containerPath, value = indexLocation path // indexLocation path is created by appending an index file name to the getJavaPluginWorkingLocation() path public SimpleLookupTable indexLocations= new SimpleLookupTable(); // key = indexLocation path, value = an index private SimpleLookupTable indexes= new SimpleLookupTable(); /* need to save ? */ private boolean needToSave= false; private IPath javaPluginLocation= null; /* can only replace a current state if its less than the new one */ // key = indexLocation path, value = index state integer private SimpleLookupTable indexStates= null; private File savedIndexNamesFile= new File(getSavedIndexesDirectory(), "savedIndexNames.txt"); //$NON-NLS-1$ private boolean javaLikeNamesChanged= true; public static final Integer SAVED_STATE= new Integer(0); public static final Integer UPDATING_STATE= new Integer(1); public static final Integer UNKNOWN_STATE= new Integer(2); public static final Integer REBUILDING_STATE= new Integer(3); // Debug public static boolean DEBUG= false; public synchronized void aboutToUpdateIndex(IPath containerPath, Integer newIndexState) { // newIndexState is either UPDATING_STATE or REBUILDING_STATE // must tag the index as inconsistent, in case we exit before the update job is started IPath indexLocation= computeIndexLocation(containerPath); Object state= getIndexStates().get(indexLocation); Integer currentIndexState= state == null ? UNKNOWN_STATE : (Integer)state; if (currentIndexState.equals(REBUILDING_STATE)) return; // already rebuilding the index int compare= newIndexState.compareTo(currentIndexState); if (compare > 0) { // so UPDATING_STATE replaces SAVED_STATE and REBUILDING_STATE replaces everything updateIndexState(indexLocation, newIndexState); } else if (compare < 0 && this.indexes.get(indexLocation) == null) { // if already cached index then there is nothing more to do rebuildIndex(indexLocation, containerPath); } } /** * Trigger addition of a resource to an index Note: the actual operation is performed in * background */ public void addBinary(IFile resource, IPath containerPath) { if (JavaCore.getPlugin() == null) return; SearchParticipant participant= SearchEngine.getDefaultSearchParticipant(); SearchDocument document= participant.getDocument(resource.getFullPath().toString()); IPath indexLocation= computeIndexLocation(containerPath); scheduleDocumentIndexing(document, containerPath, indexLocation, participant); } /** * Trigger addition of a resource to an index Note: the actual operation is performed in * background */ public void addSource(IFile resource, IPath containerPath, SourceElementParser parser) { if (JavaCore.getPlugin() == null) return; SearchParticipant participant= SearchEngine.getDefaultSearchParticipant(); SearchDocument document= participant.getDocument(resource.getFullPath().toString()); document.setParser(parser); IPath indexLocation= computeIndexLocation(containerPath); scheduleDocumentIndexing(document, containerPath, indexLocation, participant); } /* * Removes unused indexes from disk. */ public void cleanUpIndexes() { SimpleSet knownPaths= new SimpleSet(); IJavaSearchScope scope= BasicSearchEngine.createWorkspaceScope(); PatternSearchJob job= new PatternSearchJob(null, SearchEngine.getDefaultSearchParticipant(), scope, null); Index[] selectedIndexes= job.getIndexes(null); for (int i= 0, l= selectedIndexes.length; i < l; i++) { String path= selectedIndexes[i].getIndexFile().getAbsolutePath(); knownPaths.add(path); } if (this.indexStates != null) { Object[] keys= this.indexStates.keyTable; IPath[] locations= new IPath[this.indexStates.elementSize]; int count= 0; for (int i= 0, l= keys.length; i < l; i++) { IPath key= (IPath)keys[i]; if (key != null && !knownPaths.includes(key.toOSString())) locations[count++]= key; } if (count > 0) removeIndexesState(locations); } deleteIndexFiles(knownPaths); } public IPath computeIndexLocation(IPath containerPath) { IPath indexLocation= (IPath)this.indexLocations.get(containerPath); if (indexLocation == null) { String pathString= containerPath.toOSString(); CRC32 checksumCalculator= new CRC32(); checksumCalculator.update(pathString.getBytes()); String fileName= Long.toString(checksumCalculator.getValue()) + ".index"; //$NON-NLS-1$ if (VERBOSE) Util.verbose("-> index name for " + pathString + " is " + fileName); //$NON-NLS-1$ //$NON-NLS-2$ // to share the indexLocation between the indexLocations and indexStates tables, get the key from the indexStates table indexLocation= (IPath)getIndexStates().getKey(getJavaPluginWorkingLocation().append(fileName)); this.indexLocations.put(containerPath, indexLocation); } return indexLocation; } public void deleteIndexFiles() { if (DEBUG) Util.verbose("Deleting index files"); //$NON-NLS-1$ this.savedIndexNamesFile.delete(); // forget saved indexes & delete each index file deleteIndexFiles(null); } private void deleteIndexFiles(SimpleSet pathsToKeep) { File[] indexesFiles= getSavedIndexesDirectory().listFiles(); if (indexesFiles == null) return; for (int i= 0, l= indexesFiles.length; i < l; i++) { String fileName= indexesFiles[i].getAbsolutePath(); if (pathsToKeep != null && pathsToKeep.includes(fileName)) continue; String suffix= ".index"; //$NON-NLS-1$ if (fileName.regionMatches(true, fileName.length() - suffix.length(), suffix, 0, suffix.length())) { if (VERBOSE || DEBUG) Util.verbose("Deleting index file " + indexesFiles[i]); //$NON-NLS-1$ indexesFiles[i].delete(); } } } /* * Creates an empty index at the given location, for the given container path, if none exist. */ public void ensureIndexExists(IPath indexLocation, IPath containerPath) { SimpleLookupTable states= getIndexStates(); Object state= states.get(indexLocation); if (state == null) { updateIndexState(indexLocation, REBUILDING_STATE); getIndex(containerPath, indexLocation, true, true); } } public SourceElementParser getSourceElementParser(IJavaProject project, ISourceElementRequestor requestor) { // disable task tags to speed up parsing Map options= project.getOptions(true); options.put(JavaCore.COMPILER_TASK_TAGS, ""); //$NON-NLS-1$ SourceElementParser parser= new IndexingParser( requestor, new DefaultProblemFactory(Locale.getDefault()), new CompilerOptions(options), true, // index local declarations true, // optimize string literals false); // do not use source javadoc parser to speed up parsing parser.reportOnlyOneSyntaxError= true; // Always check javadoc while indexing parser.javadocParser.checkDocComment= true; parser.javadocParser.reportProblems= false; return parser; } /** * Returns the index for a given index location * * @param indexLocation The path of the index file * @return The corresponding index or <code>null</code> if not found */ public synchronized Index getIndex(IPath indexLocation) { return (Index)this.indexes.get(indexLocation); // is null if unknown, call if the containerPath must be computed } /** * Returns the index for a given project, according to the following algorithm: - if index is * already in memory: answers this one back - if (reuseExistingFile) then read it and return * this index and record it in memory - if (createIfMissing) then create a new empty index and * record it in memory * * Warning: Does not check whether index is consistent (not being used) */ public synchronized Index getIndex(IPath containerPath, boolean reuseExistingFile, boolean createIfMissing) { IPath indexLocation= computeIndexLocation(containerPath); return getIndex(containerPath, indexLocation, reuseExistingFile, createIfMissing); } /** * Returns the index for a given project, according to the following algorithm: - if index is * already in memory: answers this one back - if (reuseExistingFile) then read it and return * this index and record it in memory - if (createIfMissing) then create a new empty index and * record it in memory * * Warning: Does not check whether index is consistent (not being used) */ public synchronized Index getIndex(IPath containerPath, IPath indexLocation, boolean reuseExistingFile, boolean createIfMissing) { // Path is already canonical per construction Index index= getIndex(indexLocation); if (index == null) { Object state= getIndexStates().get(indexLocation); Integer currentIndexState= state == null ? UNKNOWN_STATE : (Integer)state; if (currentIndexState == UNKNOWN_STATE) { // should only be reachable for query jobs // IF you put an index in the cache, then AddJarFileToIndex fails because it thinks there is nothing to do rebuildIndex(indexLocation, containerPath); return null; } // index isn't cached, consider reusing an existing index file String containerPathString= containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString(); String indexLocationString= indexLocation.toOSString(); if (reuseExistingFile) { File indexFile= new File(indexLocationString); if (indexFile.exists()) { // check before creating index so as to avoid creating a new empty index if file is missing try { index= new Index(indexLocationString, containerPathString, true /*reuse index file*/); this.indexes.put(indexLocation, index); return index; } catch (IOException e) { // failed to read the existing file or its no longer compatible if (currentIndexState != REBUILDING_STATE) { // rebuild index if existing file is corrupt, unless the index is already being rebuilt if (VERBOSE) Util.verbose("-> cannot reuse existing index: " + indexLocationString + " path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$ rebuildIndex(indexLocation, containerPath); return null; } /*index = null;*/// will fall thru to createIfMissing & create a empty index for the rebuild all job to populate } } if (currentIndexState == SAVED_STATE) { // rebuild index if existing file is missing rebuildIndex(indexLocation, containerPath); return null; } } // index wasn't found on disk, consider creating an empty new one if (createIfMissing) { try { if (VERBOSE) Util.verbose("-> create empty index: " + indexLocationString + " path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$ index= new Index(indexLocationString, containerPathString, false /*do not reuse index file*/); this.indexes.put(indexLocation, index); return index; } catch (IOException e) { if (VERBOSE) Util.verbose("-> unable to create empty index: " + indexLocationString + " path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$ // The file could not be created. Possible reason: the project has been deleted. return null; } } } //System.out.println(" index name: " + path.toOSString() + " <----> " + index.getIndexFile().getName()); return index; } /** * Returns all the existing indexes for a list of index locations. Note that this may trigger * some indexes recreation work * * @param locations The list of of the index files path * @return The corresponding indexes list. */ public Index[] getIndexes(IPath[] locations, IProgressMonitor progressMonitor) { // acquire the in-memory indexes on the fly int length= locations.length; Index[] locatedIndexes= new Index[length]; int count= 0; if (this.javaLikeNamesChanged) { this.javaLikeNamesChanged= hasJavaLikeNamesChanged(); } for (int i= 0; i < length; i++) { if (progressMonitor != null && progressMonitor.isCanceled()) { throw new OperationCanceledException(); } // may trigger some index recreation work IPath indexLocation= locations[i]; Index index= getIndex(indexLocation); if (index == null) { // only need containerPath if the index must be built IPath containerPath= (IPath)this.indexLocations.keyForValue(indexLocation); if (containerPath != null) {// sanity check index= getIndex(containerPath, indexLocation, true /*reuse index file*/, false /*do not create if none*/); if (index != null && this.javaLikeNamesChanged && !index.isIndexForJar()) { // When a change in java like names extension has been detected, all // non jar files indexes (i.e. containing sources) need to be rebuilt. // see bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=286379 File indexFile= index.getIndexFile(); if (indexFile.exists()) { if (DEBUG) Util.verbose("Change in javaLikeNames - removing index file for " + containerPath); //$NON-NLS-1$ indexFile.delete(); } this.indexes.put(indexLocation, null); rebuildIndex(indexLocation, containerPath); index= null; } } } if (index != null) locatedIndexes[count++]= index; // only consider indexes which are ready } if (this.javaLikeNamesChanged) { writeJavaLikeNamesFile(); this.javaLikeNamesChanged= false; } if (count < length) { System.arraycopy(locatedIndexes, 0, locatedIndexes= new Index[count], 0, count); } return locatedIndexes; } public synchronized Index getIndexForUpdate(IPath containerPath, boolean reuseExistingFile, boolean createIfMissing) { IPath indexLocation= computeIndexLocation(containerPath); if (getIndexStates().get(indexLocation) == REBUILDING_STATE) return getIndex(containerPath, indexLocation, reuseExistingFile, createIfMissing); return null; // abort the job since the index has been removed from the REBUILDING_STATE } private SimpleLookupTable getIndexStates() { if (this.indexStates != null) return this.indexStates; this.indexStates= new SimpleLookupTable(); IPath indexesDirectoryPath= getJavaPluginWorkingLocation(); char[][] savedNames= readIndexState(indexesDirectoryPath.toOSString()); if (savedNames != null) { for (int i= 1, l= savedNames.length; i < l; i++) { // first name is saved signature, see readIndexState() char[] savedName= savedNames[i]; if (savedName.length > 0) { IPath indexLocation= indexesDirectoryPath.append(new String(savedName)); // shares indexesDirectoryPath's segments if (VERBOSE) Util.verbose("Reading saved index file " + indexLocation); //$NON-NLS-1$ this.indexStates.put(indexLocation, SAVED_STATE); } } } else { // All the index files are getting deleted and hence there is no need to // further check for change in javaLikeNames. writeJavaLikeNamesFile(); this.javaLikeNamesChanged= false; deleteIndexFiles(); } return this.indexStates; } private IPath getJavaPluginWorkingLocation() { if (this.javaPluginLocation != null) return this.javaPluginLocation; IPath stateLocation= JavaCore.getPlugin().getStateLocation(); return this.javaPluginLocation= stateLocation; } private File getSavedIndexesDirectory() { return new File(getJavaPluginWorkingLocation().toOSString()); } /* * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=286379 * Returns true if there is a change in javaLikeNames since it * has been last stored. * The javaLikeNames stored in the file javaLikeNames.txt * is compared with the current javaLikeNames and if there is a change, this * function returns true. If the file javaLikeNames.txt doesn't exist and there * is only one javaLikeName (.java), then this returns false so that no-reindexing * happens. */ private boolean hasJavaLikeNamesChanged() { char[][] currentNames= Util.getJavaLikeExtensions(); int current= currentNames.length; char[][] prevNames= readJavaLikeNamesFile(); if (prevNames == null) { if (VERBOSE && current != 1) Util.verbose("No Java like names found and there is atleast one non-default javaLikeName", System.err); //$NON-NLS-1$ return (current != 1); //Ignore if only java } int prev= prevNames.length; if (current != prev) { if (VERBOSE) Util.verbose("Java like names have changed", System.err); //$NON-NLS-1$ return true; } if (current > 1) { // Sort the current java like names. // Copy the array to avoid modifying the Util static variable System.arraycopy(currentNames, 0, currentNames= new char[current][], 0, current); Util.sort(currentNames); } // The JavaLikeNames would have been sorted before getting stored in the file, // hence just do a direct compare. for (int i= 0; i < current; i++) { if (!CharOperation.equals(currentNames[i], prevNames[i])) { if (VERBOSE) Util.verbose("Java like names have changed", System.err); //$NON-NLS-1$ return true; } } return false; } public void indexDocument(SearchDocument searchDocument, SearchParticipant searchParticipant, Index index, IPath indexLocation) { try { searchDocument.setIndex(index); searchParticipant.indexDocument(searchDocument, indexLocation); } finally { searchDocument.setIndex(null); } } /** * Trigger addition of the entire content of a project Note: the actual operation is performed * in background */ public void indexAll(IProject project) { if (JavaCore.getPlugin() == null) return; // Also request indexing of binaries on the classpath // determine the new children try { JavaModel model= JavaModelManager.getJavaModelManager().getJavaModel(); JavaProject javaProject= (JavaProject)model.getJavaProject(project); // only consider immediate libraries - each project will do the same // NOTE: force to resolve CP variables before calling indexer - 19303, so that initializers // will be run in the current thread. IClasspathEntry[] entries= javaProject.getResolvedClasspath(); for (int i= 0; i < entries.length; i++) { IClasspathEntry entry= entries[i]; if (entry.getEntryKind() == IClasspathEntry.CPE_LIBRARY) indexLibrary(entry.getPath(), project); } } catch (JavaModelException e) { // cannot retrieve classpath info } // check if the same request is not already in the queue IndexRequest request= new IndexAllProject(project, this); if (!isJobWaiting(request)) request(request); } /** * Trigger addition of a library to an index Note: the actual operation is performed in * background */ public void indexLibrary(IPath path, IProject requestingProject) { // requestingProject is no longer used to cancel jobs but leave it here just in case if (JavaCore.getPlugin() == null) return; Object target= JavaModel.getTarget(path, true); IndexRequest request= null; if (target instanceof IFile) { request= new AddJarFileToIndex((IFile)target, this); } else if (target instanceof File) { request= new AddJarFileToIndex(path, this); } else if (target instanceof IContainer) { request= new IndexBinaryFolder((IContainer)target, this); } else { return; } // check if the same request is not already in the queue if (!isJobWaiting(request)) request(request); } /** * Index the content of the given source folder. */ public void indexSourceFolder(JavaProject javaProject, IPath sourceFolder, char[][] inclusionPatterns, char[][] exclusionPatterns) { IProject project= javaProject.getProject(); if (this.jobEnd > this.jobStart) { // skip it if a job to index the project is already in the queue IndexRequest request= new IndexAllProject(project, this); if (isJobWaiting(request)) return; } request(new AddFolderToIndex(sourceFolder, project, inclusionPatterns, exclusionPatterns, this)); } public synchronized void jobWasCancelled(IPath containerPath) { IPath indexLocation= computeIndexLocation(containerPath); Index index= getIndex(indexLocation); if (index != null) { index.monitor= null; this.indexes.removeKey(indexLocation); } updateIndexState(indexLocation, UNKNOWN_STATE); } /** * Advance to the next available job, once the current one has been completed. Note: clients * awaiting until the job count is zero are still waiting at this point. */ protected synchronized void moveToNextJob() { // remember that one job was executed, and we will need to save indexes at some point this.needToSave= true; super.moveToNextJob(); } /** * No more job awaiting. */ protected void notifyIdle(long idlingTime) { if (idlingTime > 1000 && this.needToSave) saveIndexes(); } /** * Name of the background process */ public String processName() { return Messages.process_name; } private char[][] readJavaLikeNamesFile() { try { String pathName= getJavaPluginWorkingLocation().toOSString(); File javaLikeNamesFile= new File(pathName, "javaLikeNames.txt"); //$NON-NLS-1$ if (!javaLikeNamesFile.exists()) return null; char[] javaLikeNames= org.eclipse.jdt.internal.compiler.util.Util.getFileCharContent(javaLikeNamesFile, null); if (javaLikeNames.length > 0) { char[][] names= CharOperation.splitOn('\n', javaLikeNames); return names; } } catch (IOException ignored) { if (VERBOSE) Util.verbose("Failed to read javaLikeNames file"); //$NON-NLS-1$ } return null; } private void rebuildIndex(IPath indexLocation, IPath containerPath) { Object target= JavaModel.getTarget(containerPath, true); if (target == null) return; if (VERBOSE) Util.verbose("-> request to rebuild index: " + indexLocation + " path: " + containerPath); //$NON-NLS-1$ //$NON-NLS-2$ updateIndexState(indexLocation, REBUILDING_STATE); IndexRequest request= null; if (target instanceof IProject) { IProject p= (IProject)target; if (JavaProject.hasJavaNature(p)) request= new IndexAllProject(p, this); } else if (target instanceof IFolder) { request= new IndexBinaryFolder((IFolder)target, this); } else if (target instanceof IFile) { request= new AddJarFileToIndex((IFile)target, this); } else if (target instanceof File) { request= new AddJarFileToIndex(containerPath, this); } if (request != null) request(request); } /** * Recreates the index for a given path, keeping the same read-write monitor. Returns the new * empty index or null if it didn't exist before. Warning: Does not check whether index is * consistent (not being used) */ public synchronized Index recreateIndex(IPath containerPath) { // only called to over write an existing cached index... String containerPathString= containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString(); try { // Path is already canonical IPath indexLocation= computeIndexLocation(containerPath); Index index= getIndex(indexLocation); ReadWriteMonitor monitor= index == null ? null : index.monitor; if (VERBOSE) Util.verbose("-> recreating index: " + indexLocation + " for path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$ index= new Index(indexLocation.toOSString(), containerPathString, false /*do not reuse index file*/); this.indexes.put(indexLocation, index); index.monitor= monitor; return index; } catch (IOException e) { // The file could not be created. Possible reason: the project has been deleted. if (VERBOSE) { Util.verbose("-> failed to recreate index for path: " + containerPathString); //$NON-NLS-1$ e.printStackTrace(); } return null; } } /** * Trigger removal of a resource to an index Note: the actual operation is performed in * background */ public void remove(String containerRelativePath, IPath indexedContainer) { request(new RemoveFromIndex(containerRelativePath, indexedContainer, this)); } /** * Removes the index for a given path. This is a no-op if the index did not exist. */ public synchronized void removeIndex(IPath containerPath) { if (VERBOSE || DEBUG) Util.verbose("removing index " + containerPath); //$NON-NLS-1$ IPath indexLocation= computeIndexLocation(containerPath); Index index= getIndex(indexLocation); File indexFile= null; if (index != null) { index.monitor= null; indexFile= index.getIndexFile(); } if (indexFile == null) indexFile= new File(indexLocation.toOSString()); // index is not cached yet, but still want to delete the file if (indexFile.exists()) { if (DEBUG) Util.verbose("removing index file " + indexFile); //$NON-NLS-1$ indexFile.delete(); } this.indexes.removeKey(indexLocation); updateIndexState(indexLocation, null); } /** * Removes all indexes whose paths start with (or are equal to) the given path. */ public synchronized void removeIndexPath(IPath path) { if (VERBOSE || DEBUG) Util.verbose("removing index path " + path); //$NON-NLS-1$ Object[] keyTable= this.indexes.keyTable; Object[] valueTable= this.indexes.valueTable; IPath[] locations= null; int max= this.indexes.elementSize; int count= 0; for (int i= 0, l= keyTable.length; i < l; i++) { IPath indexLocation= (IPath)keyTable[i]; if (indexLocation == null) continue; if (path.isPrefixOf(indexLocation)) { Index index= (Index)valueTable[i]; index.monitor= null; if (locations == null) locations= new IPath[max]; locations[count++]= indexLocation; File indexFile= index.getIndexFile(); if (indexFile.exists()) { if (DEBUG) Util.verbose("removing index file " + indexFile); //$NON-NLS-1$ indexFile.delete(); } } else { max--; } } if (locations != null) { for (int i= 0; i < count; i++) this.indexes.removeKey(locations[i]); removeIndexesState(locations); } } /** * Removes all indexes whose paths start with (or are equal to) the given path. */ public synchronized void removeIndexFamily(IPath path) { // only finds cached index files... shutdown removes all non-cached index files ArrayList toRemove= null; Object[] containerPaths= this.indexLocations.keyTable; for (int i= 0, length= containerPaths.length; i < length; i++) { IPath containerPath= (IPath)containerPaths[i]; if (containerPath == null) continue; if (path.isPrefixOf(containerPath)) { if (toRemove == null) toRemove= new ArrayList(); toRemove.add(containerPath); } } if (toRemove != null) for (int i= 0, length= toRemove.size(); i < length; i++) removeIndex((IPath)toRemove.get(i)); } /** * Remove the content of the given source folder from the index. */ public void removeSourceFolderFromIndex(JavaProject javaProject, IPath sourceFolder, char[][] inclusionPatterns, char[][] exclusionPatterns) { IProject project= javaProject.getProject(); if (this.jobEnd > this.jobStart) { // skip it if a job to index the project is already in the queue IndexRequest request= new IndexAllProject(project, this); if (isJobWaiting(request)) return; } request(new RemoveFolderFromIndex(sourceFolder, inclusionPatterns, exclusionPatterns, project, this)); } /** * Flush current state */ public synchronized void reset() { super.reset(); if (this.indexes != null) { this.indexes= new SimpleLookupTable(); this.indexStates= null; } this.indexLocations= new SimpleLookupTable(); this.javaPluginLocation= null; } /** * Resets the index for a given path. Returns true if the index was reset, false otherwise. */ public synchronized boolean resetIndex(IPath containerPath) { // only called to over write an existing cached index... String containerPathString= containerPath.getDevice() == null ? containerPath.toString() : containerPath.toOSString(); try { // Path is already canonical IPath indexLocation= computeIndexLocation(containerPath); Index index= getIndex(indexLocation); if (VERBOSE) { Util.verbose("-> reseting index: " + indexLocation + " for path: " + containerPathString); //$NON-NLS-1$ //$NON-NLS-2$ } if (index == null) { // the index does not exist, try to recreate it return recreateIndex(containerPath) != null; } index.reset(); return true; } catch (IOException e) { // The file could not be created. Possible reason: the project has been deleted. if (VERBOSE) { Util.verbose("-> failed to reset index for path: " + containerPathString); //$NON-NLS-1$ e.printStackTrace(); } return false; } } public void saveIndex(Index index) throws IOException { // must have permission to write from the write monitor if (index.hasChanged()) { if (VERBOSE) Util.verbose("-> saving index " + index.getIndexFile()); //$NON-NLS-1$ index.save(); } synchronized (this) { IPath containerPath= new Path(index.containerPath); if (this.jobEnd > this.jobStart) { for (int i= this.jobEnd; i > this.jobStart; i--) { // skip the current job IJob job= this.awaitingJobs[i]; if (job instanceof IndexRequest) if (((IndexRequest)job).containerPath.equals(containerPath)) return; } } IPath indexLocation= computeIndexLocation(containerPath); updateIndexState(indexLocation, SAVED_STATE); } } /** * Commit all index memory changes to disk */ public void saveIndexes() { // only save cached indexes... the rest were not modified ArrayList toSave= new ArrayList(); synchronized (this) { Object[] valueTable= this.indexes.valueTable; for (int i= 0, l= valueTable.length; i < l; i++) { Index index= (Index)valueTable[i]; if (index != null) toSave.add(index); } } boolean allSaved= true; for (int i= 0, length= toSave.size(); i < length; i++) { Index index= (Index)toSave.get(i); ReadWriteMonitor monitor= index.monitor; if (monitor == null) continue; // index got deleted since acquired try { // take read lock before checking if index has changed // don't take write lock yet since it can cause a deadlock (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=50571) monitor.enterRead(); if (index.hasChanged()) { if (monitor.exitReadEnterWrite()) { try { saveIndex(index); } catch (IOException e) { if (VERBOSE) { Util.verbose("-> got the following exception while saving:", System.err); //$NON-NLS-1$ e.printStackTrace(); } allSaved= false; } finally { monitor.exitWriteEnterRead(); } } else { allSaved= false; } } } finally { monitor.exitRead(); } } this.needToSave= !allSaved; } public void scheduleDocumentIndexing(final SearchDocument searchDocument, IPath container, final IPath indexLocation, final SearchParticipant searchParticipant) { request(new IndexRequest(container, this) { public boolean execute(IProgressMonitor progressMonitor) { if (this.isCancelled || progressMonitor != null && progressMonitor.isCanceled()) return true; /* ensure no concurrent write access to index */ Index index= getIndex(this.containerPath, indexLocation, true, /*reuse index file*/true /*create if none*/); if (index == null) return true; ReadWriteMonitor monitor= index.monitor; if (monitor == null) return true; // index got deleted since acquired try { monitor.enterWrite(); // ask permission to write indexDocument(searchDocument, searchParticipant, index, indexLocation); } finally { monitor.exitWrite(); // free write lock } return true; } public String toString() { return "indexing " + searchDocument.getPath(); //$NON-NLS-1$ } }); } public String toString() { StringBuffer buffer= new StringBuffer(10); buffer.append(super.toString()); buffer.append("In-memory indexes:\n"); //$NON-NLS-1$ int count= 0; Object[] valueTable= this.indexes.valueTable; for (int i= 0, l= valueTable.length; i < l; i++) { Index index= (Index)valueTable[i]; if (index != null) buffer.append(++count).append(" - ").append(index.toString()).append('\n'); //$NON-NLS-1$ } return buffer.toString(); } private char[][] readIndexState(String dirOSString) { try { char[] savedIndexNames= org.eclipse.jdt.internal.compiler.util.Util.getFileCharContent(this.savedIndexNamesFile, null); if (savedIndexNames.length > 0) { char[][] names= CharOperation.splitOn('\n', savedIndexNames); if (names.length > 1) { // First line is DiskIndex signature + saved plugin working location (see writeSavedIndexNamesFile()) String savedSignature= DiskIndex.SIGNATURE + "+" + dirOSString; //$NON-NLS-1$ if (savedSignature.equals(new String(names[0]))) return names; } } } catch (IOException ignored) { if (VERBOSE) Util.verbose("Failed to read saved index file names"); //$NON-NLS-1$ } return null; } private synchronized void removeIndexesState(IPath[] locations) { getIndexStates(); // ensure the states are initialized int length= locations.length; boolean changed= false; for (int i= 0; i < length; i++) { if (locations[i] == null) continue; if ((this.indexStates.removeKey(locations[i]) != null)) { changed= true; if (VERBOSE) { Util.verbose("-> index state updated to: ? for: " + locations[i]); //$NON-NLS-1$ } } } if (!changed) return; writeSavedIndexNamesFile(); } private synchronized void updateIndexState(IPath indexLocation, Integer indexState) { if (indexLocation.isEmpty()) throw new IllegalArgumentException(); getIndexStates(); // ensure the states are initialized if (indexState != null) { if (indexState.equals(this.indexStates.get(indexLocation))) return; // not changed this.indexStates.put(indexLocation, indexState); } else { if (!this.indexStates.containsKey(indexLocation)) return; // did not exist anyway this.indexStates.removeKey(indexLocation); } writeSavedIndexNamesFile(); if (VERBOSE) { if (indexState == null) { Util.verbose("-> index state removed for: " + indexLocation); //$NON-NLS-1$ } else { String state= "?"; //$NON-NLS-1$ if (indexState == SAVED_STATE) state= "SAVED"; //$NON-NLS-1$ else if (indexState == UPDATING_STATE) state= "UPDATING"; //$NON-NLS-1$ else if (indexState == UNKNOWN_STATE) state= "UNKNOWN"; //$NON-NLS-1$ else if (indexState == REBUILDING_STATE) state= "REBUILDING"; //$NON-NLS-1$ Util.verbose("-> index state updated to: " + state + " for: " + indexLocation); //$NON-NLS-1$ //$NON-NLS-2$ } } } private void writeJavaLikeNamesFile() { BufferedWriter writer= null; String pathName= getJavaPluginWorkingLocation().toOSString(); try { char[][] currentNames= Util.getJavaLikeExtensions(); int length= currentNames.length; if (length > 1) { // Sort the current java like names. // Copy the array to avoid modifying the Util static variable System.arraycopy(currentNames, 0, currentNames= new char[length][], 0, length); Util.sort(currentNames); } File javaLikeNamesFile= new File(pathName, "javaLikeNames.txt"); //$NON-NLS-1$ writer= new BufferedWriter(new FileWriter(javaLikeNamesFile)); for (int i= 0; i < length - 1; i++) { writer.write(currentNames[i]); writer.write('\n'); } if (length > 0) writer.write(currentNames[length - 1]); } catch (IOException ignored) { if (VERBOSE) Util.verbose("Failed to write javaLikeNames file", System.err); //$NON-NLS-1$ } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { // ignore } } } } private void writeSavedIndexNamesFile() { BufferedWriter writer= null; try { writer= new BufferedWriter(new FileWriter(this.savedIndexNamesFile)); writer.write(DiskIndex.SIGNATURE); writer.write('+'); writer.write(getJavaPluginWorkingLocation().toOSString()); writer.write('\n'); Object[] keys= this.indexStates.keyTable; Object[] states= this.indexStates.valueTable; for (int i= 0, l= states.length; i < l; i++) { IPath key= (IPath)keys[i]; if (key != null && !key.isEmpty() && states[i] == SAVED_STATE) { writer.write(key.lastSegment()); writer.write('\n'); } } } catch (IOException ignored) { if (VERBOSE) Util.verbose("Failed to write saved index file names", System.err); //$NON-NLS-1$ } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { // ignore } } } } }