| /******************************************************************************* |
| * Copyright (c) 2000, 2017 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 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.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.Set; |
| import java.util.zip.CRC32; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IWorkspace; |
| import org.eclipse.core.resources.IWorkspaceRoot; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.dltk.compiler.CharOperation; |
| import org.eclipse.dltk.compiler.util.SimpleLookupTable; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.DLTKLanguageManager; |
| import org.eclipse.dltk.core.IDLTKLanguageToolkit; |
| import org.eclipse.dltk.core.IProjectFragment; |
| import org.eclipse.dltk.core.IScriptProject; |
| import org.eclipse.dltk.core.IShutdownListener; |
| import org.eclipse.dltk.core.ISourceElementParser; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.environment.IFileHandle; |
| import org.eclipse.dltk.core.search.BasicSearchEngine; |
| import org.eclipse.dltk.core.search.IDLTKSearchScope; |
| import org.eclipse.dltk.core.search.SearchEngine; |
| import org.eclipse.dltk.core.search.index.Index; |
| import org.eclipse.dltk.core.search.index.MixinIndex; |
| import org.eclipse.dltk.internal.core.Model; |
| import org.eclipse.dltk.internal.core.ModelManager; |
| import org.eclipse.dltk.internal.core.ScriptProject; |
| import org.eclipse.dltk.internal.core.search.PatternSearchJob; |
| import org.eclipse.dltk.internal.core.search.ProjectIndexerManager; |
| import org.eclipse.dltk.internal.core.search.processing.IJob; |
| import org.eclipse.dltk.internal.core.search.processing.JobManager; |
| import org.eclipse.dltk.internal.core.util.Messages; |
| import org.eclipse.dltk.internal.core.util.Util; |
| |
| public class IndexManager extends JobManager implements IIndexConstants { |
| |
| public SimpleLookupTable indexLocations = new SimpleLookupTable(); |
| /* |
| * key = an IPath, value = an Index |
| */ |
| private Map indexes = new HashMap(5); |
| /* need to save ? */ |
| private boolean needToSave = false; |
| private static final CRC32 checksumCalculator = new CRC32(); |
| private IPath scriptPluginLocation = null; |
| private final ListenerList<IShutdownListener> shutdownListeners = new ListenerList<>(); |
| private final ListenerList<IIndexThreadListener> indexerThreadListeners = new ListenerList<>(); |
| /* can only replace a current state if its less than the new one */ |
| private SimpleLookupTable indexStates = null; |
| private File savedIndexNamesFile = getScriptPluginWorkingLocation() |
| .append("savedIndexNames.txt").toFile(); //$NON-NLS-1$ |
| public static final Integer SAVED_STATE = Integer.valueOf(0); |
| public static final Integer UPDATING_STATE = Integer.valueOf(1); |
| public static final Integer UNKNOWN_STATE = Integer.valueOf(2); |
| public static final Integer REBUILDING_STATE = Integer.valueOf(3); |
| |
| public static final String SPECIAL = "#special#"; //$NON-NLS-1$ |
| public static final String SPECIAL_MIXIN = "#special#mixin#"; //$NON-NLS-1$ |
| public static final String SPECIAL_BUILTIN = "#special#builtin#"; //$NON-NLS-1$ |
| |
| 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 |
| String indexLocation = this.computeIndexLocation(containerPath); |
| Object state = this.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 |
| this.updateIndexState(indexLocation, newIndexState); |
| } else if (compare < 0 && this.indexes.get(indexLocation) == null) { |
| // if already cached index then there is nothing more to do |
| this.rebuildIndex(indexLocation, containerPath); |
| } |
| } |
| |
| public void addShutdownListener(IShutdownListener listener) { |
| shutdownListeners.add(listener); |
| } |
| |
| public void addIndexerThreadListener(IIndexThreadListener listener) { |
| indexerThreadListeners.add(listener); |
| } |
| |
| /* |
| * Removes unused indexes from disk. |
| */ |
| public void cleanUpIndexes() { |
| SimpleLookupTable knownPaths = new SimpleLookupTable(); |
| IDLTKSearchScope scope = BasicSearchEngine.createWorkspaceScope(null); |
| PatternSearchJob job = new PatternSearchJob(null, |
| SearchEngine.getDefaultSearchParticipant(), scope, null); |
| Index[] selectedIndexes = job.getIndexes(null); |
| for (int j = 0, max = selectedIndexes.length; j < max; j++) { |
| // TODO should use getJavaPluginWorkingLocation()+index simple name |
| // to avoid bugs such as |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=62267 |
| String path = selectedIndexes[j].getIndexFile().getAbsolutePath(); |
| knownPaths.put(path, path); |
| } |
| if (this.indexStates != null) { |
| Object[] keys = this.indexStates.keyTable; |
| int keysLength = keys.length; |
| int updates = 0; |
| String locations[] = new String[keysLength]; |
| for (int i = 0, l = keys.length; i < l; i++) { |
| String key = (String) keys[i]; |
| if (key != null && !knownPaths.containsKey(key)) { |
| locations[updates++] = key; |
| } |
| } |
| if (updates > 0) { |
| this.removeIndexesState(locations); |
| } |
| } |
| File indexesDirectory = this.getScriptPluginWorkingLocation().toFile(); |
| 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 (VERBOSE) { |
| Util.verbose( |
| "Deleting index file " + indexesFiles[i]); //$NON-NLS-1$ |
| } |
| indexesFiles[i].delete(); |
| } |
| } |
| } |
| } |
| } |
| |
| public synchronized String computeIndexLocation(IPath containerPath) { |
| String indexLocation = (String) this.indexLocations.get(containerPath); |
| if (indexLocation == null) { |
| String pathString = containerPath.toString(); |
| checksumCalculator.reset(); |
| 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$ |
| } |
| indexLocation = getScriptPluginWorkingLocation().append(fileName) |
| .toOSString(); |
| this.indexLocations.put(containerPath, indexLocation); |
| } |
| return indexLocation; |
| } |
| |
| /* |
| * Creates an empty index at the given location, for the given container |
| * path, if none exist. |
| */ |
| public void ensureIndexExists(String indexLocation, IPath containerPath) { |
| SimpleLookupTable states = this.getIndexStates(); |
| Object state = states.get(indexLocation); |
| if (state == null) { |
| this.updateIndexState(indexLocation, REBUILDING_STATE); |
| this.getIndex(containerPath, indexLocation, true, true); |
| } |
| } |
| |
| public SourceIndexerRequestor getSourceRequestor( |
| IScriptProject scriptProject) { |
| IDLTKLanguageToolkit toolkit = null; |
| toolkit = DLTKLanguageManager.getLanguageToolkit(scriptProject); |
| if (toolkit != null) { |
| return DLTKLanguageManager |
| .createSourceRequestor(toolkit.getNatureId()); |
| } |
| return null; |
| } |
| |
| /** |
| * Method to be used when <i>requestor will be set by indexer</i> |
| */ |
| public ISourceElementParser getSourceElementParser(IScriptProject project) { |
| // disable task tags to speed up parsing |
| // Map options = project.getOptions(true); |
| // options.put(DLTKCore.COMPILER_TASK_TAGS, ""); //$NON-NLS-1$ |
| IDLTKLanguageToolkit toolkit = DLTKLanguageManager |
| .getLanguageToolkit(project); |
| if (toolkit != null) { |
| return DLTKLanguageManager |
| .getSourceElementParser(toolkit.getNatureId()); |
| } |
| return null; |
| } |
| |
| /** |
| * 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) { |
| String indexLocation = this.computeIndexLocation(containerPath); |
| return this.getIndex(containerPath, indexLocation, reuseExistingFile, |
| createIfMissing); |
| } |
| |
| /** |
| * This indexes aren't required to be rebuilt. |
| * |
| * @param prefix |
| * @return |
| */ |
| public synchronized Index getSpecialIndex(String prefix, String path, |
| String containerPath) { |
| |
| final boolean mixin = prefix.equals(SPECIAL_MIXIN); |
| |
| final String indexLocation = getSpecialIndexLocation(prefix, path); |
| |
| Index index = (Index) this.indexes.get(indexLocation); |
| |
| if (index == null) { |
| final File indexFile = new File(indexLocation); |
| if (indexFile.exists()) { |
| // check before creating index so as |
| // to avoid creating a new empty |
| // index if file is missing |
| try { |
| /* reuse index file */ |
| if (mixin) { |
| index = new MixinIndex(indexLocation, containerPath, |
| true); |
| } else { |
| index = new Index(indexLocation, containerPath, true); |
| } |
| this.indexes.put(indexLocation, index); |
| return index; |
| } catch (IOException e) { |
| if (VERBOSE) { |
| Util.verbose("-> cannot reuse existing index: " //$NON-NLS-1$ |
| + indexLocation + " path: " + prefix); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| // index wasn't found on disk, consider creating an empty new one |
| |
| try { |
| if (VERBOSE) { |
| Util.verbose("-> create empty index: " + indexLocation //$NON-NLS-1$ |
| + " path: " + prefix); //$NON-NLS-1$ |
| } |
| |
| /* do not reuse index file */ |
| if (mixin) { |
| index = new MixinIndex(indexLocation, containerPath, false); |
| } else { |
| index = new Index(indexLocation, containerPath, false); |
| } |
| this.indexes.put(indexLocation, index); |
| return index; |
| } catch (IOException e) { |
| if (VERBOSE) { |
| Util.verbose("-> unable to create empty index: " //$NON-NLS-1$ |
| + indexLocation + " path: " + containerPath); //$NON-NLS-1$ |
| } |
| // The file could not be created. Possible reason: the |
| // project has been deleted. |
| return null; |
| } |
| } |
| return index; |
| } |
| |
| public String getSpecialIndexLocation(String prefix, String path) { |
| return computeIndexLocation(new Path(prefix + path)); |
| } |
| |
| /** |
| * 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, |
| String indexLocation, boolean reuseExistingFile, |
| boolean createIfMissing) { |
| boolean mixin = containerPath.toString().startsWith(SPECIAL_MIXIN); |
| // Path is already canonical per construction |
| Index index = (Index) this.indexes.get(indexLocation); |
| if (index == null) { |
| Object state = this.getIndexStates().get(indexLocation); |
| Integer currentIndexState = state == null ? UNKNOWN_STATE |
| : (Integer) state; |
| if (currentIndexState.equals(UNKNOWN_STATE)) { |
| // should only be reachable for query jobs |
| // IF you put an index in the cache, then AddArchiveFileToIndex |
| // fails because it thinks there is nothing to do |
| if (!createIfMissing) { |
| this.rebuildIndex(indexLocation, containerPath); |
| return null; |
| } |
| // if (!mixin) { |
| // } |
| } |
| // index isn't cached, consider reusing an existing index file |
| String containerPathString = containerPath.toString(); |
| if (reuseExistingFile) { |
| File indexFile = new File(indexLocation); |
| if (indexFile.exists()) { // check before creating index so as |
| // to avoid creating a new empty |
| // index if file is missing |
| try { |
| if (mixin) { |
| index = new MixinIndex(indexLocation, |
| containerPathString, true); |
| /* reuse index file */ |
| } else { |
| index = new Index(indexLocation, |
| 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: " //$NON-NLS-1$ |
| + indexLocation + " path: " //$NON-NLS-1$ |
| + containerPathString); |
| } |
| if (!createIfMissing) { |
| this.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 |
| if (!createIfMissing) { |
| this.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: " + indexLocation //$NON-NLS-1$ |
| + " path: " + containerPathString); //$NON-NLS-1$ |
| } |
| if (mixin) { |
| index = new MixinIndex(indexLocation, |
| containerPathString, false); |
| /* do not reuse index file */ |
| } else { |
| index = new Index(indexLocation, 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: " //$NON-NLS-1$ |
| + indexLocation + " path: " //$NON-NLS-1$ |
| + containerPathString); |
| } |
| // 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; |
| } |
| |
| public synchronized Index getIndex(String indexLocation) { |
| return (Index) this.indexes.get(indexLocation); |
| /* |
| * is null if unknown, call if the containerPath must be computed |
| */ |
| } |
| |
| public synchronized Index getIndexForUpdate(IPath containerPath, |
| boolean reuseExistingFile, boolean createIfMissing) { |
| String indexLocation = this.computeIndexLocation(containerPath); |
| if (this.getIndexStates().get(indexLocation) == REBUILDING_STATE) { |
| return this.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(); |
| char[] savedIndexNames = this.readIndexState(); |
| if (savedIndexNames.length > 0) { |
| char[][] names = CharOperation.splitOn('\n', savedIndexNames); |
| if (names.length > 0) { |
| // check to see if workspace has moved, if so then do not trust |
| // saved indexes |
| File indexesDirectory = getScriptPluginWorkingLocation() |
| .toFile(); |
| char[] dirName = indexesDirectory.getAbsolutePath() |
| .toCharArray(); |
| int delimiterPos = dirName.length; |
| if (CharOperation.match(names[0], 0, delimiterPos, dirName, 0, |
| delimiterPos, true)) { |
| 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); |
| } |
| } |
| } else { |
| this.savedIndexNamesFile.delete(); // forget saved indexes & |
| // delete each index file |
| File[] files = indexesDirectory.listFiles(); |
| if (files != null) { |
| for (int i = 0, l = files.length; i < l; i++) { |
| String fileName = files[i].getAbsolutePath(); |
| if (fileName.toLowerCase().endsWith(".index")) { //$NON-NLS-1$ |
| if (VERBOSE) { |
| Util.verbose( |
| "Deleting index file " + files[i]); //$NON-NLS-1$ |
| } |
| files[i].delete(); |
| } |
| } |
| } |
| } |
| } |
| } |
| return this.indexStates; |
| } |
| |
| private IPath getScriptPluginWorkingLocation() { |
| if (this.scriptPluginLocation != null) { |
| return this.scriptPluginLocation; |
| } |
| IPath stateLocation = DLTKCore.getPlugin().getStateLocation(); |
| return this.scriptPluginLocation = stateLocation; |
| } |
| |
| public void jobWasCancelled(IPath containerPath) { |
| String indexLocation = this.computeIndexLocation(containerPath); |
| Object o = this.indexes.get(indexLocation); |
| if (o instanceof Index) { |
| ((Index) o).monitor = null; |
| this.indexes.remove(indexLocation); |
| } |
| this.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. |
| */ |
| @Override |
| 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(); |
| } |
| |
| @Override |
| protected void notifyIdle() { |
| for (IIndexThreadListener listener : indexerThreadListeners) { |
| listener.aboutToBeIdle(); |
| } |
| } |
| |
| /** |
| * No more job awaiting. |
| */ |
| @Override |
| protected void notifyIdle(long idlingTime) { |
| if (idlingTime > 1000 && this.needToSave) { |
| this.saveIndexes(); |
| } |
| for (IIndexThreadListener listener : indexerThreadListeners) { |
| listener.aboutToBeRun(idlingTime); |
| } |
| } |
| |
| /** |
| * Name of the background process |
| */ |
| @Override |
| public String processName() { |
| return Messages.process_name; |
| } |
| |
| private void rebuildIndex(String indexLocation, IPath containerPath) { |
| IWorkspace workspace = ResourcesPlugin.getWorkspace(); |
| if (workspace == null) { |
| return; |
| } |
| |
| String cp = containerPath.toString(); |
| if (cp.startsWith(SPECIAL_MIXIN)) { |
| cp = cp.substring(SPECIAL_MIXIN.length()); |
| } |
| Path pathCP = new Path(cp); |
| |
| Object target = Model.getTarget(workspace.getRoot(), pathCP, true); |
| // Try to search for specified container path using model |
| if (target == null || target instanceof IFileHandle) { |
| try { |
| IScriptProject[] scriptProjects = ModelManager.getModelManager() |
| .getModel().getScriptProjects(); |
| for (IScriptProject project : scriptProjects) { |
| IProjectFragment[] fragments = project |
| .getProjectFragments(); |
| for (IProjectFragment fragment : fragments) { |
| if (fragment.getPath().equals(pathCP)) { |
| target = fragment; |
| break; |
| } |
| } |
| if (target != null) { |
| break; |
| } |
| } |
| } catch (ModelException e) { |
| DLTKCore.error("Failed to obtain list of DLTK projects", e); |
| } |
| } |
| if (target == null) { |
| return; |
| } |
| |
| if (VERBOSE) { |
| Util.verbose("-> request to rebuild index: " + indexLocation //$NON-NLS-1$ |
| + " path: " + containerPath.toString()); //$NON-NLS-1$ |
| } |
| |
| this.updateIndexState(indexLocation, REBUILDING_STATE); |
| IndexRequest request = null; |
| if (target instanceof IProject) { |
| IProject p = (IProject) target; |
| if (ScriptProject.hasScriptNature(p)) { |
| // request = new IndexAllProject(p, this); |
| ProjectIndexerManager.indexProject(p); |
| } |
| } else if (target instanceof IProjectFragment) { |
| ProjectIndexerManager.indexProjectFragment( |
| ((IProjectFragment) target).getScriptProject(), pathCP); |
| return; |
| } else if (target instanceof IFile) { |
| // request = new AddArchiveFileToIndex((IFile) target, this); |
| return; |
| } else if (target instanceof IFileHandle) { |
| // request = new AddArchiveFileToIndex(containerPath, this); |
| return; |
| } |
| if (request != null) { |
| this.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) { |
| boolean mixin = containerPath.toString().startsWith(SPECIAL_MIXIN); |
| // only called to over write an existing cached index... |
| String containerPathString = containerPath.toString(); |
| try { |
| // Path is already canonical |
| String indexLocation = this.computeIndexLocation(containerPath); |
| Index index = (Index) this.indexes.get(indexLocation); |
| ReadWriteMonitor monitor = index == null ? null : index.monitor; |
| if (VERBOSE) { |
| Util.verbose("-> recreating index: " + indexLocation //$NON-NLS-1$ |
| + " for path: " + containerPathString); //$NON-NLS-1$ |
| } |
| if (mixin) { |
| index = new MixinIndex(indexLocation, containerPathString, |
| false); |
| /* reuse index file */ |
| } else { |
| index = new Index(indexLocation, 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: " //$NON-NLS-1$ |
| + containerPathString); |
| 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) { |
| this.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) { |
| Util.verbose("removing index " + containerPath); //$NON-NLS-1$ |
| } |
| String indexLocation = this.computeIndexLocation(containerPath); |
| File indexFile = new File(indexLocation); |
| if (indexFile.exists()) { |
| indexFile.delete(); |
| } |
| final Object o = this.indexes.remove(indexLocation); |
| if (o instanceof Index) { |
| final Index index = (Index) o; |
| index.monitor = null; |
| if (index.isRebuildable()) { |
| this.updateIndexState(indexLocation, null); |
| } |
| } |
| } |
| |
| /** |
| * Removes all indexes whose paths start with (or are equal to) the given |
| * path. |
| */ |
| public synchronized void removeIndexPath(IPath path) { |
| Set keySet = this.indexes.keySet(); |
| Iterator keys = keySet.iterator(); |
| String[] locations = null; |
| int max = keySet.size(); |
| int ptr = 0; |
| while (keys.hasNext()) { |
| String indexLocation = (String) keys.next(); |
| IPath indexPath = new Path(indexLocation); |
| if (path.isPrefixOf(indexPath)) { |
| Index index = (Index) this.indexes.get(indexLocation); |
| if (index != null) { |
| index.monitor = null; |
| } |
| if (locations == null) { |
| locations = new String[max]; |
| } |
| locations[ptr++] = indexLocation; |
| File indexFile = new File(indexLocation); |
| if (indexFile.exists()) { |
| indexFile.delete(); |
| } |
| } else if (locations == null) { |
| max--; |
| } |
| } |
| if (locations != null) { |
| for (int i = 0; i < ptr; i++) { |
| this.indexes.remove(locations[i]); |
| } |
| this.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++) { |
| this.removeIndex((IPath) toRemove.get(i)); |
| } |
| } |
| } |
| |
| public void removeIndexerThreadListener(IIndexThreadListener listener) { |
| indexerThreadListeners.remove(listener); |
| } |
| |
| /** |
| * Remove the content of the given source folder from the index. |
| */ |
| public void removeSourceFolderFromIndex(ScriptProject scriptProject, |
| IPath sourceFolder, char[][] inclusionPatterns, |
| char[][] exclusionPatterns) { |
| IProject project = scriptProject.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); |
| // ProjectIndexerManager.indexProject(project); |
| // if (this.isJobWaiting(request)) { |
| // return; |
| // } |
| } |
| this.request(new RemoveFolderFromIndex(sourceFolder, inclusionPatterns, |
| exclusionPatterns, project, this)); |
| } |
| |
| /** |
| * Flush current state |
| */ |
| @Override |
| public synchronized void reset() { |
| super.reset(); |
| if (this.indexes != null) { |
| this.indexes = new HashMap(5); |
| this.indexStates = null; |
| } |
| this.indexLocations = new SimpleLookupTable(); |
| this.scriptPluginLocation = null; |
| } |
| |
| public synchronized 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(); |
| } |
| if (!index.isRebuildable()) { |
| return; |
| } |
| // TODO should use getJavaPluginWorkingLocation()+index simple name to |
| // avoid bugs such as |
| // https://bugs.eclipse.org/bugs/show_bug.cgi?id=62267 |
| String indexLocation = index.getIndexFile().getPath(); |
| if (this.jobEnd > this.jobStart) { |
| Object containerPath = this.indexLocations |
| .keyForValue(indexLocation); |
| if (containerPath != null) { |
| synchronized (this) { |
| 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; |
| } |
| } |
| } |
| } |
| } |
| } |
| this.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) { |
| for (Iterator iter = this.indexes.values().iterator(); iter |
| .hasNext();) { |
| Object o = iter.next(); |
| if (o instanceof Index) { |
| toSave.add(o); |
| } |
| } |
| } |
| 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 { |
| this.saveIndex(index); |
| } catch (IOException e) { |
| if (VERBOSE) { |
| Util.verbose( |
| "-> got the following exception while saving:", //$NON-NLS-1$ |
| System.err); |
| e.printStackTrace(); |
| } |
| allSaved = false; |
| } finally { |
| monitor.exitWriteEnterRead(); |
| } |
| } else { |
| allSaved = false; |
| } |
| } |
| } finally { |
| monitor.exitRead(); |
| } |
| } |
| this.needToSave = !allSaved; |
| } |
| |
| @Override |
| public void shutdown() { |
| super.shutdown(); |
| for (IShutdownListener listener : shutdownListeners) { |
| listener.shutdown(); |
| } |
| shutdownListeners.clear(); |
| } |
| |
| @Override |
| 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()) //$NON-NLS-1$ |
| .append('\n'); |
| } |
| return buffer.toString(); |
| } |
| |
| private char[] readIndexState() { |
| try { |
| return org.eclipse.dltk.compiler.util.Util |
| .getFileCharContent(this.savedIndexNamesFile, null); |
| } catch (IOException ignored) { |
| if (VERBOSE) { |
| Util.verbose("Failed to read saved index file names"); //$NON-NLS-1$ |
| } |
| return new char[0]; |
| } |
| } |
| |
| private synchronized void removeIndexesState(String[] locations) { |
| this.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: " //$NON-NLS-1$ |
| + locations[i]); |
| } |
| } |
| } |
| if (!changed) { |
| return; |
| } |
| this.writeSavedIndexNamesFile(); |
| } |
| |
| private synchronized void updateIndexState(String indexLocation, |
| Integer indexState) { |
| this.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); |
| } |
| this.writeSavedIndexNamesFile(); |
| if (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$ |
| } |
| Util.verbose("-> index state updated to: " + state + " for: " //$NON-NLS-1$ //$NON-NLS-2$ |
| + indexLocation); |
| } |
| } |
| |
| private void writeSavedIndexNamesFile() { |
| BufferedWriter writer = null; |
| try { |
| writer = new BufferedWriter(new FileWriter(savedIndexNamesFile)); |
| Object[] keys = this.indexStates.keyTable; |
| Object[] states = this.indexStates.valueTable; |
| for (int i = 0, l = states.length; i < l; i++) { |
| if (states[i] == SAVED_STATE) { |
| writer.write((String) keys[i]); |
| writer.write('\n'); |
| } |
| } |
| } catch (IOException ignored) { |
| if (VERBOSE) { |
| Util.verbose("Failed to write saved index file names", //$NON-NLS-1$ |
| System.err); |
| } |
| } finally { |
| if (writer != null) { |
| try { |
| writer.close(); |
| } catch (IOException e) { |
| // ignore |
| } |
| } |
| } |
| } |
| |
| public synchronized void rebuild() { |
| this.disable(); |
| File indexesDirectory = this.getScriptPluginWorkingLocation().toFile(); |
| // this. |
| if (indexesDirectory.isDirectory()) { |
| File[] indexesFiles = indexesDirectory.listFiles(); |
| if (indexesFiles != null) { |
| for (int i = 0, indexesFilesLength = indexesFiles.length; i < indexesFilesLength; i++) { |
| indexesFiles[i].delete(); |
| } |
| } |
| } |
| this.reset(); |
| // this.indexAll(project) |
| |
| this.enable(); |
| IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); |
| IProject[] projects = root.getProjects(); |
| for (int i = 0; i < projects.length; i++) { |
| if (DLTKLanguageManager.hasScriptNature(projects[i])) { |
| // this.indexAll(projects[i]); |
| ProjectIndexerManager.indexProject(projects[i]); |
| } |
| } |
| } |
| } |