blob: 54d1cd19f861bae2f67b3d9574ec1f7e714cde43 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.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.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.CRC32;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.internal.core.CharOperation;
import org.eclipse.cdt.internal.core.index.IIndex;
import org.eclipse.cdt.internal.core.index.impl.Index;
import org.eclipse.cdt.internal.core.model.CProject;
import org.eclipse.cdt.internal.core.search.CWorkspaceScope;
import org.eclipse.cdt.internal.core.search.IndexSelector;
import org.eclipse.cdt.internal.core.search.SimpleLookupTable;
import org.eclipse.cdt.internal.core.search.processing.IJob;
import org.eclipse.cdt.internal.core.search.processing.JobManager;
import org.eclipse.cdt.internal.core.sourcedependency.UpdateDependency;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
public class IndexManager extends JobManager implements IIndexConstants {
/* number of file contents in memory */
public static int MAX_FILES_IN_MEMORY = 0;
public IWorkspace workspace;
public SimpleLookupTable indexNames = new SimpleLookupTable();
private Map indexes = new HashMap(5);
/* read write monitors */
private Map monitors = new HashMap(5);
/* need to save ? */
private boolean needToSave = false;
private static final CRC32 checksumCalculator = new CRC32();
private IPath cCorePluginLocation = null;
/* can only replace a current state if its less than the new one */
private SimpleLookupTable indexStates = null;
private File savedIndexNamesFile =
new File(getCCorePluginWorkingLocation().append("savedIndexNames.txt").toOSString()); //$NON-NLS-1$
public static Integer SAVED_STATE = new Integer(0);
public static Integer UPDATING_STATE = new Integer(1);
public static Integer UNKNOWN_STATE = new Integer(2);
public static Integer REBUILDING_STATE = new Integer(3);
public static boolean VERBOSE = false;
public synchronized void aboutToUpdateIndex(IPath path, 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
String indexName = computeIndexName(path);
Object state = getIndexStates().get(indexName);
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(indexName, newIndexState);
} else if (compare < 0 && this.indexes.get(path) == null) {
// if already cached index then there is nothing more to do
rebuildIndex(indexName, path);
}
}
/**
* Not at the moment...
* @param resource
* @param indexedContainer
*/
/*
public void addBinary(IFile resource, IPath indexedContainer){
if (JavaCore.getPlugin() == null) return;
AddClassFileToIndex job = new AddClassFileToIndex(resource, indexedContainer, this);
if (this.awaitingJobsCount() < MAX_FILES_IN_MEMORY) {
// reduces the chance that the file is open later on, preventing it from being deleted
if (!job.initializeContents()) return;
}
request(job);
}
*/
/**
* Trigger addition of a resource to an index
* Note: the actual operation is performed in background
*/
public void addSource(IFile resource, IPath indexedContainer){
if (CCorePlugin.getDefault() == null) return;
AddCompilationUnitToIndex job = new AddCompilationUnitToIndex(resource, indexedContainer, this);
if (this.awaitingJobsCount() < MAX_FILES_IN_MEMORY) {
// reduces the chance that the file is open later on, preventing it from being deleted
if (!job.initializeContents()) return;
}
request(job);
}
public void updateDependencies(IResource resource){
if (CCorePlugin.getDefault() == null) return;
UpdateDependency job = new UpdateDependency(resource);
request(job);
}
String computeIndexName(IPath path) {
String name = (String) indexNames.get(path);
if (name == null) {
String pathString = path.toOSString();
checksumCalculator.reset();
checksumCalculator.update(pathString.getBytes());
String fileName = Long.toString(checksumCalculator.getValue()) + ".index"; //$NON-NLS-1$
if (IndexManager.VERBOSE)
JobManager.verbose("-> index name for " + pathString + " is " + fileName); //$NON-NLS-1$ //$NON-NLS-2$
name = getCCorePluginWorkingLocation().append(fileName).toOSString();
indexNames.put(path, name);
}
return name;
}
/**
* 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 IIndex getIndex(IPath path, boolean reuseExistingFile, boolean createIfMissing) {
// Path is already canonical per construction
IIndex index = (IIndex) indexes.get(path);
if (index == null) {
String indexName = computeIndexName(path);
Object state = getIndexStates().get(indexName);
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(indexName, path);
return null;
}
// index isn't cached, consider reusing an existing index file
if (reuseExistingFile) {
File indexFile = new File(indexName);
if (indexFile.exists()) { // check before creating index so as to avoid creating a new empty index if file is missing
try {
index = new Index(indexName, "Index for " + path.toOSString(), true /*reuse index file*/); //$NON-NLS-1$
indexes.put(path, index);
monitors.put(index, new ReadWriteMonitor());
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 (IndexManager.VERBOSE)
JobManager.verbose("-> cannot reuse existing index: "+indexName+" path: "+path.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$
rebuildIndex(indexName, path);
return null;
} else {
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(indexName, path);
return null;
}
}
// index wasn't found on disk, consider creating an empty new one
if (createIfMissing) {
try {
if (VERBOSE)
JobManager.verbose("-> create empty index: "+indexName+" path: "+path.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$
index = new Index(indexName, "Index for " + path.toOSString(), false /*do not reuse index file*/); //$NON-NLS-1$
indexes.put(path, index);
monitors.put(index, new ReadWriteMonitor());
return index;
} catch (IOException e) {
if (VERBOSE)
JobManager.verbose("-> unable to create empty index: "+indexName+" path: "+path.toOSString()); //$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;
}
private SimpleLookupTable getIndexStates() {
if (indexStates != null) return indexStates;
this.indexStates = new SimpleLookupTable();
char[] savedIndexNames = readIndexState();
if (savedIndexNames.length > 0) {
char[][] names = CharOperation.splitOn('\n', savedIndexNames);
for (int i = 0, l = names.length; i < l; i++) {
char[] name = names[i];
if (name.length > 0)
this.indexStates.put(new String(name), SAVED_STATE);
}
}
return this.indexStates;
}
private IPath getCCorePluginWorkingLocation() {
if (this.cCorePluginLocation != null) return this.cCorePluginLocation;
return this.cCorePluginLocation = CCorePlugin.getDefault().getStateLocation();
}
/**
* Index access is controlled through a read-write monitor so as
* to ensure there is no concurrent read and write operations
* (only concurrent reading is allowed).
*/
public ReadWriteMonitor getMonitorFor(IIndex index){
return (ReadWriteMonitor) monitors.get(index);
}
/**
* Trigger addition of the entire content of a project
* Note: the actual operation is performed in background
*/
public void indexAll(IProject project) {
if (CCorePlugin.getDefault() == null) return;
// check if the same request is not already in the queue
IndexRequest request = new IndexAllProject(project, this);
for (int i = this.jobEnd; i > this.jobStart; i--) // NB: don't check job at jobStart, as it may have already started (see http://bugs.eclipse.org/bugs/show_bug.cgi?id=32488)
if (request.equals(this.awaitingJobs[i])) return;
this.request(request);
}
/**
* Index the content of the given source folder.
*/
public void indexSourceFolder(CProject javaProject, IPath sourceFolder, final char[][] exclusionPattern) {
IProject project = javaProject.getProject();
if (this.jobEnd > this.jobStart) {
// check if a job to index the project is not already in the queue
IndexRequest request = new IndexAllProject(project, this);
for (int i = this.jobEnd; i > this.jobStart; i--) // NB: don't check job at jobStart, as it may have already started (see http://bugs.eclipse.org/bugs/show_bug.cgi?id=32488)
if (request.equals(this.awaitingJobs[i])) return;
}
this.request(new AddFolderToIndex(sourceFolder, project, exclusionPattern, this));
}
public void jobWasCancelled(IPath path) {
Object o = this.indexes.get(path);
if (o instanceof IIndex) {
this.monitors.remove(o);
this.indexes.remove(path);
}
updateIndexState(computeIndexName(path), 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
needToSave = true;
super.moveToNextJob();
}
/**
* No more job awaiting.
*/
protected void notifyIdle(long idlingTime){
if (idlingTime > 1000 && needToSave) saveIndexes();
}
/*
* For debug purpose
*/
public IIndex peekAtIndex(IPath path) {
return (IIndex) indexes.get(path);
}
/**
* Name of the background process
*/
public String processName(){
return org.eclipse.cdt.internal.core.Util.bind("process.name"); //$NON-NLS-1$
}
private void rebuildIndex(String indexName, IPath path) {
Object target = org.eclipse.cdt.internal.core.Util.getTarget(ResourcesPlugin.getWorkspace().getRoot(), path, true);
if (target == null) return;
if (IndexManager.VERBOSE)
JobManager.verbose("-> request to rebuild index: "+indexName+" path: "+path.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$
updateIndexState(indexName, REBUILDING_STATE);
IndexRequest request = null;
if (target instanceof IProject) {
IProject p = (IProject) target;
request = new IndexAllProject(p, 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 IIndex recreateIndex(IPath path) {
// only called to over write an existing cached index...
try {
IIndex index = (IIndex) this.indexes.get(path);
ReadWriteMonitor monitor = (ReadWriteMonitor) this.monitors.remove(index);
// Path is already canonical
String indexPath = computeIndexName(path);
if (IndexManager.VERBOSE)
JobManager.verbose("-> recreating index: "+indexPath+" for path: "+path.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$
index = new Index(indexPath, "Index for " + path.toOSString(), false /*reuse index file*/); //$NON-NLS-1$
indexes.put(path, index);
monitors.put(index, monitor);
return index;
} catch (IOException e) {
// The file could not be created. Possible reason: the project has been deleted.
if (IndexManager.VERBOSE) {
JobManager.verbose("-> failed to recreate index for path: "+path.toOSString()); //$NON-NLS-1$ //$NON-NLS-2$
e.printStackTrace();
}
return null;
}
}
/**
* Trigger removal of a resource to an index
* Note: the actual operation is performed in background
*/
public void remove(String resourceName, IPath indexedContainer){
request(new RemoveFromIndex(resourceName, 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 path) {
if (IndexManager.VERBOSE)
JobManager.verbose("removing index " + path); //$NON-NLS-1$
String indexName = computeIndexName(path);
File indexFile = new File(indexName);
if (indexFile.exists())
indexFile.delete();
Object o = this.indexes.get(path);
if (o instanceof IIndex)
this.monitors.remove(o);
this.indexes.remove(path);
updateIndexState(indexName, null);
}
/**
* 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;
Iterator iterator = this.indexes.keySet().iterator();
while (iterator.hasNext()) {
IPath indexPath = (IPath) iterator.next();
if (path.isPrefixOf(indexPath)) {
if (toRemove == null)
toRemove = new ArrayList();
toRemove.add(indexPath);
}
}
if (toRemove != null)
for (int i = 0, length = toRemove.size(); i < length; i++)
this.removeIndex((IPath) toRemove.get(i));
}
/**
* Remove the content of the given source folder from the index.
*/
public void removeSourceFolderFromIndex(CProject javaProject, IPath sourceFolder, char[][] exclusionPatterns) {
IProject project = javaProject.getProject();
if (this.jobEnd > this.jobStart) {
// check if a job to index the project is not already in the queue
IndexRequest request = new IndexAllProject(project, this);
for (int i = this.jobEnd; i > this.jobStart; i--) // NB: don't check job at jobStart, as it may have already started (see http://bugs.eclipse.org/bugs/show_bug.cgi?id=32488)
if (request.equals(this.awaitingJobs[i])) return;
}
this.request(new RemoveFolderFromIndex(sourceFolder, exclusionPatterns, project, this));
}
/**
* Flush current state
*/
public void reset() {
super.reset();
if (this.indexes != null) {
this.indexes = new HashMap(5);
this.monitors = new HashMap(5);
this.indexStates = null;
}
this.indexNames = new SimpleLookupTable();
this.cCorePluginLocation = null;
}
public void saveIndex(IIndex index) throws IOException {
// must have permission to write from the write monitor
if (index.hasChanged()) {
if (IndexManager.VERBOSE)
JobManager.verbose("-> saving index " + index.getIndexFile()); //$NON-NLS-1$
index.save();
}
String indexName = index.getIndexFile().getPath();
if (this.jobEnd > this.jobStart) {
Object indexPath = indexNames.keyForValue(indexName);
if (indexPath != null) {
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).indexPath.equals(indexPath)) return;
}
}
}
updateIndexState(indexName, 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) {
for (Iterator iter = this.indexes.values().iterator(); iter.hasNext();) {
Object o = iter.next();
if (o instanceof IIndex)
toSave.add(o);
}
}
for (int i = 0, length = toSave.size(); i < length; i++) {
IIndex index = (IIndex) toSave.get(i);
ReadWriteMonitor monitor = getMonitorFor(index);
if (monitor == null) continue; // index got deleted since acquired
try {
monitor.enterWrite();
try {
saveIndex(index);
} catch(IOException e){
if (IndexManager.VERBOSE) {
JobManager.verbose("-> got the following exception while saving:"); //$NON-NLS-1$
e.printStackTrace();
}
//Util.log(e);
}
} finally {
monitor.exitWrite();
}
}
needToSave = false;
}
public void shutdown() {
if (IndexManager.VERBOSE)
JobManager.verbose("Shutdown"); //$NON-NLS-1$
//Get index entries for all projects in the workspace, store their absolute paths
IndexSelector indexSelector = new IndexSelector(new CWorkspaceScope(), null, false, this);
IIndex[] selectedIndexes = indexSelector.getIndexes();
SimpleLookupTable knownPaths = new SimpleLookupTable();
for (int i = 0, max = selectedIndexes.length; i < max; i++) {
String path = selectedIndexes[i].getIndexFile().getAbsolutePath();
knownPaths.put(path, path);
}
//Any index entries that are in the index state must have a corresponding
//path entry - if not they are removed from the saved indexes file
if (indexStates != null) {
Object[] indexNames = indexStates.keyTable;
for (int i = 0, l = indexNames.length; i < l; i++) {
String key = (String) indexNames[i];
if (key != null && !knownPaths.containsKey(key)) //here is an index that is in t
updateIndexState(key, null);
}
}
//Clean up the .metadata folder - if there are any files in the directory that
//are not associated to an index we delete them
File indexesDirectory = new File(getCCorePluginWorkingLocation().toOSString());
if (indexesDirectory.isDirectory()) {
File[] indexesFiles = indexesDirectory.listFiles();
if (indexesFiles != null) {
for (int i = 0, indexesFilesLength = indexesFiles.length; i < indexesFilesLength; i++) {
String fileName = indexesFiles[i].getAbsolutePath();
if (!knownPaths.containsKey(fileName) && fileName.toLowerCase().endsWith(".index")) { //$NON-NLS-1$
if (IndexManager.VERBOSE)
JobManager.verbose("Deleting index file " + indexesFiles[i]); //$NON-NLS-1$
indexesFiles[i].delete();
}
}
}
}
super.shutdown();
}
public String toString() {
StringBuffer buffer = new StringBuffer(10);
buffer.append(super.toString());
buffer.append("In-memory indexes:\n"); //$NON-NLS-1$
int count = 0;
for (Iterator iter = this.indexes.values().iterator(); iter.hasNext();) {
buffer.append(++count).append(" - ").append(iter.next().toString()).append('\n'); //$NON-NLS-1$
}
return buffer.toString();
}
private char[] readIndexState() {
try {
return org.eclipse.cdt.internal.core.Util.getFileCharContent(savedIndexNamesFile, null);
} catch (IOException ignored) {
if (IndexManager.VERBOSE)
JobManager.verbose("Failed to read saved index file names"); //$NON-NLS-1$
return new char[0];
}
}
private void updateIndexState(String indexName, Integer indexState) {
getIndexStates(); // ensure the states are initialized
if (indexState != null) {
if (indexState.equals(indexStates.get(indexName))) return; // not changed
indexStates.put(indexName, indexState);
} else {
if (!indexStates.containsKey(indexName)) return; // did not exist anyway
indexStates.removeKey(indexName);
}
BufferedWriter writer = null;
try {
writer = new BufferedWriter(new FileWriter(savedIndexNamesFile));
Object[] indexNames = indexStates.keyTable;
Object[] states = indexStates.valueTable;
for (int i = 0, l = states.length; i < l; i++) {
if (states[i] == SAVED_STATE) {
writer.write((String) indexNames[i]);
writer.write('\n');
}
}
} catch (IOException ignored) {
if (IndexManager.VERBOSE)
JobManager.verbose("Failed to write saved index file names"); //$NON-NLS-1$
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {}
}
}
if (IndexManager.VERBOSE) {
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$
JobManager.verbose("-> index state updated to: " + state + " for: "+indexName); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}