/*******************************************************************************
* 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
}
}
}
}
}