2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.util.indexing;
19 import com.intellij.AppTopics;
20 import com.intellij.history.LocalHistory;
21 import com.intellij.ide.caches.CacheUpdater;
22 import com.intellij.lang.ASTNode;
23 import com.intellij.notification.NotificationDisplayType;
24 import com.intellij.notification.NotificationGroup;
25 import com.intellij.notification.NotificationType;
26 import com.intellij.openapi.application.ApplicationAdapter;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.PathManager;
29 import com.intellij.openapi.components.ApplicationComponent;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.Document;
32 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
33 import com.intellij.openapi.editor.impl.EditorHighlighterCache;
34 import com.intellij.openapi.extensions.Extensions;
35 import com.intellij.openapi.fileEditor.FileDocumentManager;
36 import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
37 import com.intellij.openapi.fileTypes.*;
38 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
39 import com.intellij.openapi.module.Module;
40 import com.intellij.openapi.module.ModuleManager;
41 import com.intellij.openapi.progress.*;
42 import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
43 import com.intellij.openapi.project.*;
44 import com.intellij.openapi.roots.*;
45 import com.intellij.openapi.util.*;
46 import com.intellij.openapi.util.io.FileUtil;
47 import com.intellij.openapi.vfs.*;
48 import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
49 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
50 import com.intellij.openapi.vfs.newvfs.ManagingFS;
51 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
52 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
53 import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
54 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
55 import com.intellij.psi.*;
56 import com.intellij.psi.impl.PsiDocumentTransactionListener;
57 import com.intellij.psi.impl.source.PsiFileImpl;
58 import com.intellij.psi.search.EverythingGlobalScope;
59 import com.intellij.psi.search.GlobalSearchScope;
60 import com.intellij.psi.stubs.SerializationManager;
61 import com.intellij.util.*;
62 import com.intellij.util.concurrency.Semaphore;
63 import com.intellij.util.containers.ConcurrentHashSet;
64 import com.intellij.util.containers.ContainerUtil;
65 import com.intellij.util.containers.HashMap;
66 import com.intellij.util.containers.HashSet;
67 import com.intellij.util.io.*;
68 import com.intellij.util.io.storage.HeavyProcessLatch;
69 import com.intellij.util.messages.MessageBus;
70 import com.intellij.util.messages.MessageBusConnection;
71 import gnu.trove.TIntHashSet;
72 import gnu.trove.TIntIterator;
73 import gnu.trove.TIntProcedure;
74 import gnu.trove.TObjectIntHashMap;
75 import org.jetbrains.annotations.NonNls;
76 import org.jetbrains.annotations.NotNull;
77 import org.jetbrains.annotations.Nullable;
82 import java.util.concurrent.ConcurrentLinkedQueue;
83 import java.util.concurrent.ScheduledFuture;
84 import java.util.concurrent.atomic.AtomicBoolean;
85 import java.util.concurrent.atomic.AtomicInteger;
88 * @author Eugene Zhuravlev
92 public class FileBasedIndex implements ApplicationComponent {
93 private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.FileBasedIndex");
95 private static final String CORRUPTION_MARKER_NAME = "corruption.marker";
96 private final Map<ID<?, ?>, Pair<UpdatableIndex<?, ?, FileContent>, InputFilter>> myIndices = new HashMap<ID<?, ?>, Pair<UpdatableIndex<?, ?, FileContent>, InputFilter>>();
97 private final Map<ID<?, ?>, Semaphore> myUnsavedDataIndexingSemaphores = new HashMap<ID<?,?>, Semaphore>();
98 private final TObjectIntHashMap<ID<?, ?>> myIndexIdToVersionMap = new TObjectIntHashMap<ID<?, ?>>();
99 private final Set<ID<?, ?>> myNotRequiringContentIndices = new HashSet<ID<?, ?>>();
100 private final Set<ID<?, ?>> myRequiringContentIndices = new HashSet<ID<?, ?>>();
101 private final Set<FileType> myNoLimitCheckTypes = new HashSet<FileType>();
103 private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap();
104 private final ChangedFilesCollector myChangedFilesCollector;
106 private final List<IndexableFileSet> myIndexableSets = ContainerUtil.createEmptyCOWList();
107 private final Map<IndexableFileSet, Project> myIndexableSetToProjectMap = new HashMap<IndexableFileSet, Project>();
109 private static final int OK = 1;
110 private static final int REQUIRES_REBUILD = 2;
111 private static final int REBUILD_IN_PROGRESS = 3;
112 private static final Map<ID<?, ?>, AtomicInteger> ourRebuildStatus = new HashMap<ID<?,?>, AtomicInteger>();
114 private final VirtualFileManagerEx myVfManager;
115 private final FileDocumentManager myFileDocumentManager;
116 private final FileTypeManager myFileTypeManager;
117 private final ConcurrentHashSet<ID<?, ?>> myUpToDateIndices = new ConcurrentHashSet<ID<?, ?>>();
118 private final Map<Document, PsiFile> myTransactionMap = new HashMap<Document, PsiFile>();
120 private static final int ALREADY_PROCESSED = 0x02;
121 @Nullable private final String myConfigPath;
122 @Nullable private final String mySystemPath;
123 private final boolean myIsUnitTestMode;
124 private ScheduledFuture<?> myFlushingFuture;
125 private volatile int myLocalModCount;
127 public void requestReindex(final VirtualFile file) {
128 myChangedFilesCollector.invalidateIndices(file, true);
131 public void requestReindexExcluded(final VirtualFile file) {
132 myChangedFilesCollector.invalidateIndices(file, false);
135 public interface InputFilter {
136 boolean acceptInput(VirtualFile file);
139 public FileBasedIndex(final VirtualFileManagerEx vfManager, FileDocumentManager fdm,
140 FileTypeManager fileTypeManager, MessageBus bus, SerializationManager sm /*need this parameter to ensure component dependency*/) throws IOException {
141 myVfManager = vfManager;
142 myFileDocumentManager = fdm;
143 myFileTypeManager = fileTypeManager;
144 myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
145 myConfigPath = calcConfigPath(PathManager.getConfigPath());
146 mySystemPath = calcConfigPath(PathManager.getSystemPath());
148 final MessageBusConnection connection = bus.connect();
149 connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() {
151 public void transactionStarted(final Document doc, final PsiFile file) {
153 synchronized (myTransactionMap) {
154 myTransactionMap.put(doc, file);
156 myUpToDateIndices.clear();
161 public void transactionCompleted(final Document doc, final PsiFile file) {
162 synchronized (myTransactionMap) {
163 myTransactionMap.remove(doc);
168 connection.subscribe(FileTypeManager.TOPIC, new FileTypeListener() {
169 private Map<FileType, Set<String>> myTypeToExtensionMap;
171 public void beforeFileTypesChanged(final FileTypeEvent event) {
172 cleanupProcessedFlag();
173 myTypeToExtensionMap = new HashMap<FileType, Set<String>>();
174 for (FileType type : myFileTypeManager.getRegisteredFileTypes()) {
175 myTypeToExtensionMap.put(type, getExtensions(type));
180 public void fileTypesChanged(final FileTypeEvent event) {
181 final Map<FileType, Set<String>> oldExtensions = myTypeToExtensionMap;
182 myTypeToExtensionMap = null;
183 if (oldExtensions != null) {
184 final Map<FileType, Set<String>> newExtensions = new HashMap<FileType, Set<String>>();
185 for (FileType type : myFileTypeManager.getRegisteredFileTypes()) {
186 newExtensions.put(type, getExtensions(type));
188 // we are interested only in extension changes or removals.
189 // addition of an extension is handled separately by RootsChanged event
190 if (!newExtensions.keySet().containsAll(oldExtensions.keySet())) {
194 for (Map.Entry<FileType, Set<String>> entry : oldExtensions.entrySet()) {
195 FileType fileType = entry.getKey();
196 Set<String> strings = entry.getValue();
197 if (!newExtensions.get(fileType).containsAll(strings)) {
205 private Set<String> getExtensions(FileType type) {
206 final Set<String> set = new HashSet<String>();
207 for (FileNameMatcher matcher : myFileTypeManager.getAssociations(type)) {
208 set.add(matcher.getPresentableString());
213 private void rebuildAllIndices() {
214 for (ID<?, ?> indexId : myIndices.keySet()) {
218 catch (StorageException e) {
222 scheduleIndexRebuild(true);
226 connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
228 public void before(List<? extends VFileEvent> events) {
229 for (VFileEvent event : events) {
230 final Object requestor = event.getRequestor();
231 if (requestor instanceof FileDocumentManager || requestor instanceof PsiManager || requestor == LocalHistory.VFS_EVENT_REQUESTOR) {
232 cleanupMemoryStorage();
239 public void after(List<? extends VFileEvent> events) {
243 connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
245 public void fileContentReloaded(VirtualFile file, Document document) {
246 cleanupMemoryStorage();
250 public void unsavedDocumentsDropped() {
251 cleanupMemoryStorage();
255 ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() {
257 public void writeActionStarted(Object action) {
258 myUpToDateIndices.clear();
262 myChangedFilesCollector = new ChangedFilesCollector();
265 final File workInProgressFile = getMarkerFile();
266 if (workInProgressFile.exists()) {
267 // previous IDEA session was closed incorrectly, so drop all indices
268 FileUtil.delete(PathManager.getIndexRoot());
273 final FileBasedIndexExtension[] extensions = Extensions.getExtensions(FileBasedIndexExtension.EXTENSION_POINT_NAME);
274 for (FileBasedIndexExtension<?, ?> extension : extensions) {
275 ourRebuildStatus.put(extension.getName(), new AtomicInteger(OK));
278 final File corruptionMarker = new File(PathManager.getIndexRoot(), CORRUPTION_MARKER_NAME);
279 final boolean currentVersionCorrupted = corruptionMarker.exists();
280 boolean versionChanged = false;
281 for (FileBasedIndexExtension<?, ?> extension : extensions) {
282 versionChanged |= registerIndexer(extension, currentVersionCorrupted);
284 FileUtil.delete(corruptionMarker);
285 String rebuildNotification = null;
286 if (currentVersionCorrupted) {
287 rebuildNotification = "Index files on disk are corrupted. Indices will be rebuilt.";
289 else if (versionChanged) {
290 rebuildNotification = "Index file format has changed for some indices. These indices will be rebuilt.";
292 if (rebuildNotification != null && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
293 new NotificationGroup("Indexing", NotificationDisplayType.BALLOON, false)
294 .createNotification("Index Rebuild", rebuildNotification, NotificationType.INFORMATION, null).notify(null);
296 dropUnregisteredIndices();
298 // check if rebuild was requested for any index during registration
299 for (ID<?, ?> indexId : myIndices.keySet()) {
300 if (ourRebuildStatus.get(indexId).compareAndSet(REQUIRES_REBUILD, OK)) {
304 catch (StorageException e) {
305 requestRebuild(indexId);
311 myVfManager.addVirtualFileListener(myChangedFilesCollector);
313 registerIndexableSet(new AdditionalIndexableFileSet(), null);
316 ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
322 //FileUtil.createIfDoesntExist(workInProgressFile);
323 saveRegisteredIndices(myIndices.keySet());
324 myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() {
325 int lastModCount = 0;
328 if (lastModCount == myLocalModCount) {
329 flushAllIndices(lastModCount);
331 lastModCount = myLocalModCount;
339 public void initComponent() {
342 private static String calcConfigPath(final String path) {
344 final String _path = FileUtil.toSystemIndependentName(new File(path).getCanonicalPath());
345 return _path.endsWith("/")? _path : _path + "/" ;
347 catch (IOException e) {
353 private static class FileBasedIndexHolder {
354 private static final FileBasedIndex ourInstance = ApplicationManager.getApplication().getComponent(FileBasedIndex.class);
357 public static FileBasedIndex getInstance() {
358 return FileBasedIndexHolder.ourInstance;
362 * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted @param extension
363 * @param isCurrentVersionCorrupted
365 private <K, V> boolean registerIndexer(final FileBasedIndexExtension<K, V> extension, final boolean isCurrentVersionCorrupted) throws IOException {
366 final ID<K, V> name = extension.getName();
367 final int version = extension.getVersion();
368 final File versionFile = IndexInfrastructure.getVersionFile(name);
369 final boolean versionFileExisted = versionFile.exists();
370 boolean versionChanged = false;
371 if (isCurrentVersionCorrupted || IndexInfrastructure.versionDiffers(versionFile, version)) {
372 if (!isCurrentVersionCorrupted && versionFileExisted) {
373 versionChanged = true;
374 LOG.info("Version has changed for index " + extension.getName() + ". The index will be rebuilt.");
376 FileUtil.delete(IndexInfrastructure.getIndexRootDir(name));
377 IndexInfrastructure.rewriteVersion(versionFile, version);
380 for (int attempt = 0; attempt < 2; attempt++) {
382 final MapIndexStorage<K, V> storage = new MapIndexStorage<K, V>(IndexInfrastructure.getStorageFile(name), extension.getKeyDescriptor(), extension.getValueExternalizer(), extension.getCacheSize());
383 final MemoryIndexStorage<K, V> memStorage = new MemoryIndexStorage<K, V>(storage);
384 final UpdatableIndex<K, V, FileContent> index = createIndex(name, extension, memStorage);
385 final InputFilter inputFilter = extension.getInputFilter();
387 assert inputFilter != null : "Index extension " + name + " must provide non-null input filter";
389 myIndices.put(name, new Pair<UpdatableIndex<?,?, FileContent>, InputFilter>(index, new IndexableFilesFilter(inputFilter)));
390 myUnsavedDataIndexingSemaphores.put(name, new Semaphore());
391 myIndexIdToVersionMap.put(name, version);
392 if (!extension.dependsOnFileContent()) {
393 myNotRequiringContentIndices.add(name);
396 myRequiringContentIndices.add(name);
398 myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable());
401 catch (IOException e) {
403 FileUtil.delete(IndexInfrastructure.getIndexRootDir(name));
404 IndexInfrastructure.rewriteVersion(versionFile, version);
407 return versionChanged;
410 private static void saveRegisteredIndices(Collection<ID<?, ?>> ids) {
411 final File file = getRegisteredIndicesFile();
413 FileUtil.createIfDoesntExist(file);
414 final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
416 os.writeInt(ids.size());
417 for (ID<?, ?> id : ids) {
418 IOUtil.writeString(id.toString(), os);
425 catch (IOException ignored) {
429 private static Set<String> readRegistsredIndexNames() {
430 final Set<String> result = new HashSet<String>();
432 final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile())));
434 final int size = in.readInt();
435 for (int idx = 0; idx < size; idx++) {
436 result.add(IOUtil.readString(in));
443 catch (IOException ignored) {
448 private static File getRegisteredIndicesFile() {
449 return new File(PathManager.getIndexRoot(), "registered");
452 private <K, V> UpdatableIndex<K, V, FileContent> createIndex(final ID<K, V> indexId, final FileBasedIndexExtension<K, V> extension, final MemoryIndexStorage<K, V> storage) throws IOException {
453 final MapReduceIndex<K, V, FileContent> index;
454 if (extension instanceof CustomImplementationFileBasedIndexExtension) {
455 final UpdatableIndex<K, V, FileContent> custom = ((CustomImplementationFileBasedIndexExtension<K, V, FileContent>)extension).createIndexImplementation(indexId, this, storage);
457 assert custom != null : "Custom index implementation must not be null; index: " + indexId;
459 if (!(custom instanceof MapReduceIndex)) {
462 index = (MapReduceIndex<K,V, FileContent>)custom;
465 index = new MapReduceIndex<K, V, FileContent>(indexId, extension.getIndexer(), storage);
468 final KeyDescriptor<K> keyDescriptor = extension.getKeyDescriptor();
469 index.setInputIdToDataKeysIndex(new Factory<PersistentHashMap<Integer, Collection<K>>>() {
471 public PersistentHashMap<Integer, Collection<K>> create() {
473 return createIdToDataKeysIndex(indexId, keyDescriptor, storage);
475 catch (IOException e) {
476 throw new RuntimeException(e);
484 private static <K> PersistentHashMap<Integer, Collection<K>> createIdToDataKeysIndex(final ID<K, ?> indexId,
485 final KeyDescriptor<K> keyDescriptor,
486 MemoryIndexStorage<K, ?> storage) throws IOException {
487 final File indexStorageFile = IndexInfrastructure.getInputIndexStorageFile(indexId);
488 final Ref<Boolean> isBufferingMode = new Ref<Boolean>(false);
489 final Map<Integer, Collection<K>> tempMap = new HashMap<Integer, Collection<K>>();
491 final DataExternalizer<Collection<K>> dataExternalizer = new DataExternalizer<Collection<K>>() {
493 public void save(DataOutput out, Collection<K> value) throws IOException {
495 DataInputOutputUtil.writeINT(out, value.size());
496 for (K key : value) {
497 keyDescriptor.save(out, key);
500 catch (IllegalArgumentException e) {
501 throw new IOException("Error saving data for index " + indexId, e);
506 public Collection<K> read(DataInput in) throws IOException {
508 final int size = DataInputOutputUtil.readINT(in);
509 final List<K> list = new ArrayList<K>(size);
510 for (int idx = 0; idx < size; idx++) {
511 list.add(keyDescriptor.read(in));
515 catch (IllegalArgumentException e) {
516 throw new IOException("Error reading data for index " + indexId, e);
521 // Important! Update IdToDataKeysIndex depending on the sate of "buffering" flag from the MemoryStorage.
522 // If buffering is on, all changes should be done in memory (similar to the way it is done in memory storage).
523 // Otherwise data in IdToDataKeysIndex will not be in sync with the 'main' data in the index on disk and index updates will be based on the
524 // wrong sets of keys for the given file. This will lead to unpretictable results in main index because it will not be
525 // cleared properly before updating (removed data will still be present on disk). See IDEA-52223 for illustration of possible effects.
527 final PersistentHashMap<Integer, Collection<K>> map = new PersistentHashMap<Integer, Collection<K>>(
528 indexStorageFile, new EnumeratorIntegerDescriptor(), dataExternalizer
532 protected Collection<K> doGet(Integer integer) throws IOException {
533 if (isBufferingMode.get()) {
534 final Collection<K> collection = tempMap.get(integer);
535 if (collection != null) {
539 return super.doGet(integer);
543 protected void doPut(Integer integer, Collection<K> ks) throws IOException {
544 if (isBufferingMode.get()) {
545 tempMap.put(integer, ks == null? Collections.<K>emptySet() : ks);
548 super.doPut(integer, ks);
553 protected void doRemove(Integer integer) throws IOException {
554 if (isBufferingMode.get()) {
555 tempMap.put(integer, Collections.<K>emptySet());
558 super.doRemove(integer);
563 storage.addBufferingStateListsner(new MemoryIndexStorage.BufferingStateListener() {
565 public void bufferingStateChanged(boolean newState) {
567 isBufferingMode.set(newState);
571 public void memoryStorageCleared() {
583 public String getComponentName() {
584 return "FileBasedIndex";
588 public void disposeComponent() {
592 private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(false);
594 private void performShutdown() {
595 if (!myShutdownPerformed.compareAndSet(false, true)) {
596 return; // already shut down
599 if (myFlushingFuture != null) {
600 myFlushingFuture.cancel(false);
601 myFlushingFuture = null;
604 myFileDocumentManager.saveAllDocuments();
607 LOG.info("START INDEX SHUTDOWN");
609 myChangedFilesCollector.forceUpdate(null, null, null, true);
611 for (ID<?, ?> indexId : myIndices.keySet()) {
612 final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
613 assert index != null;
614 checkRebuild(indexId, true); // if the index was scheduled for rebuild, only clean it
615 //LOG.info("DISPOSING " + indexId);
619 myVfManager.removeVirtualFileListener(myChangedFilesCollector);
621 //FileUtil.delete(getMarkerFile());
623 catch (Throwable e) {
624 LOG.info("Problems during index shutdown", e);
625 throw new RuntimeException(e);
627 LOG.info("END INDEX SHUTDOWN");
631 private void flushAllIndices(final long modCount) {
632 if (HeavyProcessLatch.INSTANCE.isRunning()) {
635 IndexingStamp.flushCache();
636 for (ID<?, ?> indexId : new ArrayList<ID<?, ?>>(myIndices.keySet())) {
637 if (HeavyProcessLatch.INSTANCE.isRunning() || modCount != myLocalModCount) {
638 return; // do not interfere with 'main' jobs
641 final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
646 catch (StorageException e) {
648 requestRebuild(indexId);
652 if (!HeavyProcessLatch.INSTANCE.isRunning() && modCount == myLocalModCount) { // do not interfere with 'main' jobs
653 SerializationManager.getInstance().flushNameStorage();
658 * @param project it is guaranteeed to return data which is up-to-date withing the project
659 * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist
662 public <K> Collection<K> getAllKeys(final ID<K, ?> indexId, @NotNull Project project) {
663 Set<K> allKeys = new HashSet<K>();
664 processAllKeys(indexId, new CommonProcessors.CollectProcessor<K>(allKeys), project);
669 * @param project it is guaranteeed to return data which is up-to-date withing the project
670 * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist
672 public <K> boolean processAllKeys(final ID<K, ?> indexId, Processor<K> processor, @Nullable Project project) {
674 final UpdatableIndex<K, ?, FileContent> index = getIndex(indexId);
678 ensureUpToDate(indexId, project, project != null? GlobalSearchScope.allScope(project) : new EverythingGlobalScope());
679 return index.processAllKeys(processor);
681 catch (StorageException e) {
682 scheduleRebuild(indexId, e);
684 catch (RuntimeException e) {
685 final Throwable cause = e.getCause();
686 if (cause instanceof StorageException || cause instanceof IOException) {
687 scheduleRebuild(indexId, cause);
697 private static final ThreadLocal<Integer> myUpToDateCheckState = new ThreadLocal<Integer>();
699 public static void disableUpToDateCheckForCurrentThread() {
700 final Integer currentValue = myUpToDateCheckState.get();
701 myUpToDateCheckState.set(currentValue == null? 1 : currentValue.intValue() + 1);
704 public static void enableUpToDateCheckForCurrentThread() {
705 final Integer currentValue = myUpToDateCheckState.get();
706 if (currentValue != null) {
707 final int newValue = currentValue.intValue() - 1;
709 myUpToDateCheckState.set(newValue);
712 myUpToDateCheckState.remove();
717 private static boolean isUpToDateCheckEnabled() {
718 final Integer value = myUpToDateCheckState.get();
719 return value == null || value.intValue() == 0;
723 private final ThreadLocal<Boolean> myReentrancyGuard = new ThreadLocal<Boolean>() {
725 protected Boolean initialValue() {
726 return Boolean.FALSE;
731 * DO NOT CALL DIRECTLY IN CLIENT CODE
732 * The method is internal to indexing engine end is called internally. The method is public due to implementation details
734 public <K> void ensureUpToDate(final ID<K, ?> indexId, @Nullable Project project, @Nullable GlobalSearchScope filter) {
735 ensureUpToDate(indexId, project, filter, null);
738 private <K> void ensureUpToDate(final ID<K, ?> indexId, @Nullable Project project, @Nullable GlobalSearchScope filter,
739 @Nullable VirtualFile restrictedFile) {
740 if (!needsFileContentLoading(indexId)) {
741 return; //indexed eagerly in foreground while building unindexed file list
743 if (isDumb(project)) {
744 handleDumbMode(project);
747 if (myReentrancyGuard.get().booleanValue()) {
748 //assert false : "ensureUpToDate() is not reentrant!";
751 myReentrancyGuard.set(Boolean.TRUE);
754 myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
755 if (isUpToDateCheckEnabled()) {
757 checkRebuild(indexId, false);
758 myChangedFilesCollector.forceUpdate(project, filter, restrictedFile, false);
759 indexUnsavedDocuments(indexId, project, filter, restrictedFile);
761 catch (StorageException e) {
762 scheduleRebuild(indexId, e);
764 catch (RuntimeException e) {
765 final Throwable cause = e.getCause();
766 if (cause instanceof StorageException || cause instanceof IOException) {
767 scheduleRebuild(indexId, e);
776 myReentrancyGuard.set(Boolean.FALSE);
780 private static void handleDumbMode(@Nullable Project project) {
781 ProgressManager.checkCanceled(); // DumbModeAction.CANCEL
783 if (project != null) {
784 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
785 if (progressIndicator instanceof BackgroundableProcessIndicator) {
786 final BackgroundableProcessIndicator indicator = (BackgroundableProcessIndicator)progressIndicator;
787 if (indicator.getDumbModeAction() == DumbModeAction.WAIT) {
788 assert !ApplicationManager.getApplication().isDispatchThread();
789 DumbService.getInstance(project).waitForSmartMode();
795 throw new IndexNotReadyException();
798 private static boolean isDumb(@Nullable Project project) {
799 if (project != null) {
800 return DumbServiceImpl.getInstance(project).isDumb();
802 for (Project proj : ProjectManager.getInstance().getOpenProjects()) {
803 if (DumbServiceImpl.getInstance(proj).isDumb()) {
811 public <K, V> List<V> getValues(final ID<K, V> indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) {
812 final List<V> values = new SmartList<V>();
813 processValuesImpl(indexId, dataKey, true, null, new ValueProcessor<V>() {
815 public boolean process(final VirtualFile file, final V value) {
824 public <K, V> Collection<VirtualFile> getContainingFiles(final ID<K, V> indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) {
825 final Set<VirtualFile> files = new HashSet<VirtualFile>();
826 processValuesImpl(indexId, dataKey, false, null, new ValueProcessor<V>() {
828 public boolean process(final VirtualFile file, final V value) {
837 public interface ValueProcessor<V> {
839 * @param value a value to process
840 * @param file the file the value came from
841 * @return false if no further processing is needed, true otherwise
843 boolean process(VirtualFile file, V value);
847 * @return false if ValueProcessor.process() returned false; true otherwise or if ValueProcessor was not called at all
849 public <K, V> boolean processValues(final ID<K, V> indexId, @NotNull final K dataKey, @Nullable final VirtualFile inFile,
850 ValueProcessor<V> processor, @NotNull final GlobalSearchScope filter) {
851 return processValuesImpl(indexId, dataKey, false, inFile, processor, filter);
857 private <K, V, R> R processExceptions(final ID<K, V> indexId,
858 @Nullable final VirtualFile restrictToFile,
859 final GlobalSearchScope filter,
860 ThrowableConvertor<UpdatableIndex<K, V, FileContent>, R, StorageException> computable) {
862 final UpdatableIndex<K, V, FileContent> index = getIndex(indexId);
866 final Project project = filter.getProject();
867 //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries";
868 ensureUpToDate(indexId, project, filter, restrictToFile);
871 index.getReadLock().lock();
872 return computable.convert(index);
875 index.getReadLock().unlock();
878 catch (StorageException e) {
879 scheduleRebuild(indexId, e);
881 catch (RuntimeException e) {
882 final Throwable cause = getCauseToRebuildIndex(e);
884 scheduleRebuild(indexId, cause);
893 private <K, V> boolean processValuesImpl(final ID<K, V> indexId, final K dataKey, final boolean ensureValueProcessedOnce,
894 @Nullable final VirtualFile restrictToFile, final ValueProcessor<V> processor,
895 final GlobalSearchScope filter) {
896 ThrowableConvertor<UpdatableIndex<K, V, FileContent>, Boolean, StorageException> keyProcessor = new ThrowableConvertor<UpdatableIndex<K, V, FileContent>, Boolean, StorageException>() {
898 public Boolean convert(UpdatableIndex<K, V, FileContent> index) throws StorageException {
899 final ValueContainer<V> container = index.getData(dataKey);
901 boolean shouldContinue = true;
903 if (restrictToFile != null) {
904 if (restrictToFile instanceof VirtualFileWithId) {
905 final int restrictedFileId = getFileId(restrictToFile);
906 for (final Iterator<V> valueIt = container.getValueIterator(); valueIt.hasNext(); ) {
907 final V value = valueIt.next();
908 if (container.isAssociated(value, restrictedFileId)) {
909 shouldContinue = processor.process(restrictToFile, value);
910 if (!shouldContinue) {
918 final PersistentFS fs = (PersistentFS)ManagingFS.getInstance();
919 VALUES_LOOP: for (final Iterator<V> valueIt = container.getValueIterator(); valueIt.hasNext();) {
920 final V value = valueIt.next();
921 for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) {
922 final int id = inputIdsIterator.next();
923 VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
924 if (file != null && filter.accept(file)) {
925 shouldContinue = processor.process(file, value);
926 if (!shouldContinue) {
929 if (ensureValueProcessedOnce) {
930 break; // continue with the next value
936 return shouldContinue;
939 final Boolean result = processExceptions(indexId, restrictToFile, filter, keyProcessor);
940 return result == null || result.booleanValue();
943 public <K, V> void processFilesContainingAllKeys(final ID<K, V> indexId,
944 final Collection<K> dataKeys,
945 final GlobalSearchScope filter,
946 @Nullable Condition<V> valueChecker,
947 final Processor<VirtualFile> processor) {
948 final TIntHashSet set = collectFileIdsContainingAllKeys(indexId, dataKeys, filter, valueChecker);
950 processVirtualFiles(set, filter, processor);
955 public <K, V> TIntHashSet collectFileIdsContainingAllKeys(final ID<K, V> indexId,
956 final Collection<K> dataKeys,
957 final GlobalSearchScope filter,
958 @Nullable final Condition<V> valueChecker) {
959 final ThrowableConvertor<UpdatableIndex<K, V, FileContent>, TIntHashSet, StorageException> convertor =
960 new ThrowableConvertor<UpdatableIndex<K, V, FileContent>, TIntHashSet, StorageException>() {
963 public TIntHashSet convert(UpdatableIndex<K, V, FileContent> index) throws StorageException {
964 TIntHashSet mainIntersection = null;
966 for (K dataKey : dataKeys) {
967 ProgressManager.checkCanceled();
968 TIntHashSet copy = new TIntHashSet();
969 final ValueContainer<V> container = index.getData(dataKey);
971 for (final Iterator<V> valueIt = container.getValueIterator(); valueIt.hasNext(); ) {
972 final V value = valueIt.next();
973 if (valueChecker != null && !valueChecker.value(value)) {
976 for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext(); ) {
977 final int id = inputIdsIterator.next();
978 if (mainIntersection == null || mainIntersection.contains(id)) {
984 mainIntersection = copy;
985 if (mainIntersection.isEmpty()) {
986 return new TIntHashSet();
990 return mainIntersection;
995 return processExceptions(indexId, null, filter, convertor);
998 public static boolean processVirtualFiles(TIntHashSet ids, final GlobalSearchScope filter, final Processor<VirtualFile> processor) {
999 final PersistentFS fs = (PersistentFS)ManagingFS.getInstance();
1000 return ids.forEach(new TIntProcedure() {
1002 public boolean execute(int id) {
1003 ProgressManager.checkCanceled();
1004 VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
1005 if (file != null && filter.accept(file)) {
1006 return processor.process(file);
1013 public static @Nullable Throwable getCauseToRebuildIndex(RuntimeException e) {
1014 Throwable cause = e.getCause();
1015 if (cause instanceof StorageException || cause instanceof IOException ||
1016 cause instanceof IllegalArgumentException || cause instanceof IllegalStateException) return cause;
1020 public <K, V> boolean getFilesWithKey(final ID<K, V> indexId, final Set<K> dataKeys, Processor<VirtualFile> processor, GlobalSearchScope filter) {
1022 final UpdatableIndex<K, V, FileContent> index = getIndex(indexId);
1023 if (index == null) {
1026 final Project project = filter.getProject();
1027 //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries";
1028 ensureUpToDate(indexId, project, filter);
1031 index.getReadLock().lock();
1032 final List<TIntHashSet> locals = new ArrayList<TIntHashSet>();
1033 for (K dataKey : dataKeys) {
1034 TIntHashSet local = new TIntHashSet();
1036 final ValueContainer<V> container = index.getData(dataKey);
1038 for (final Iterator<V> valueIt = container.getValueIterator(); valueIt.hasNext();) {
1039 final V value = valueIt.next();
1040 for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) {
1041 final int id = inputIdsIterator.next();
1047 if (locals.isEmpty()) {
1051 Collections.sort(locals, new Comparator<TIntHashSet>() {
1053 public int compare(TIntHashSet o1, TIntHashSet o2) {
1054 return o1.size() - o2.size();
1058 final PersistentFS fs = (PersistentFS)ManagingFS.getInstance();
1059 TIntIterator ids = join(locals).iterator();
1060 while (ids.hasNext()) {
1061 int id = ids.next();
1062 //VirtualFile file = IndexInfrastructure.findFileById(fs, id);
1063 VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
1064 if (file != null && filter.accept(file)) {
1065 if (!processor.process(file)) {
1072 index.getReadLock().unlock();
1075 catch (StorageException e) {
1076 scheduleRebuild(indexId, e);
1078 catch (RuntimeException e) {
1079 final Throwable cause = e.getCause();
1080 if (cause instanceof StorageException || cause instanceof IOException) {
1081 scheduleRebuild(indexId, cause);
1090 private static TIntHashSet join(List<TIntHashSet> locals) {
1091 TIntHashSet result = locals.get(0);
1092 if (locals.size() > 1) {
1093 TIntIterator it = result.iterator();
1095 while (it.hasNext()) {
1097 for (int i = 1; i < locals.size(); i++) {
1098 if (!locals.get(i).contains(id)) {
1108 public <K> void scheduleRebuild(final ID<K, ?> indexId, final Throwable e) {
1110 requestRebuild(indexId);
1112 checkRebuild(indexId, false);
1114 catch (ProcessCanceledException ignored) {
1118 private void checkRebuild(final ID<?, ?> indexId, final boolean cleanupOnly) {
1119 final AtomicInteger status = ourRebuildStatus.get(indexId);
1120 if (status.get() == OK) return;
1121 if (status.compareAndSet(REQUIRES_REBUILD, REBUILD_IN_PROGRESS)) {
1122 cleanupProcessedFlag();
1124 final Runnable rebuildRunnable = new Runnable() {
1128 clearIndex(indexId);
1130 scheduleIndexRebuild(false);
1133 catch (StorageException e) {
1134 requestRebuild(indexId);
1138 status.compareAndSet(REBUILD_IN_PROGRESS, OK);
1143 if (cleanupOnly || myIsUnitTestMode) {
1144 rebuildRunnable.run();
1147 SwingUtilities.invokeLater(new Runnable() {
1150 new Task.Modal(null, "Updating index", false) {
1152 public void run(@NotNull final ProgressIndicator indicator) {
1153 indicator.setIndeterminate(true);
1154 rebuildRunnable.run();
1162 if (status.get() == REBUILD_IN_PROGRESS) {
1163 throw new ProcessCanceledException();
1167 private void scheduleIndexRebuild(boolean forceDumbMode) {
1168 for (Project project : ProjectManager.getInstance().getOpenProjects()) {
1169 final Set<CacheUpdater> updatersToRun = Collections.<CacheUpdater>singleton(new UnindexedFilesUpdater(project, this));
1170 final DumbServiceImpl service = DumbServiceImpl.getInstance(project);
1171 if (forceDumbMode) {
1172 service.queueCacheUpdateInDumbMode(updatersToRun);
1175 service.queueCacheUpdate(updatersToRun);
1180 private void clearIndex(final ID<?, ?> indexId) throws StorageException {
1181 final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1182 assert index != null: "Index with key " + indexId + " not found or not registered properly";
1185 IndexInfrastructure.rewriteVersion(IndexInfrastructure.getVersionFile(indexId), myIndexIdToVersionMap.get(indexId));
1187 catch (IOException e) {
1192 private Set<Document> getUnsavedOrTransactedDocuments() {
1193 final Set<Document> docs = new HashSet<Document>(Arrays.asList(myFileDocumentManager.getUnsavedDocuments()));
1194 synchronized (myTransactionMap) {
1195 docs.addAll(myTransactionMap.keySet());
1200 private void indexUnsavedDocuments(ID<?, ?> indexId, @Nullable Project project, GlobalSearchScope filter,
1201 VirtualFile restrictedFile) throws StorageException {
1202 if (myUpToDateIndices.contains(indexId)) {
1203 return; // no need to index unsaved docs
1206 final Set<Document> documents = getUnsavedOrTransactedDocuments();
1207 if (!documents.isEmpty()) {
1208 // now index unsaved data
1209 final StorageGuard.Holder guard = setDataBufferingEnabled(true);
1211 final Semaphore semaphore = myUnsavedDataIndexingSemaphores.get(indexId);
1213 assert semaphore != null : "Semaphore for unsaved data indexing was not initialized for index " + indexId;
1216 boolean allDocsProcessed = true;
1218 for (Document document : documents) {
1219 allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter, restrictedFile);
1225 while (!semaphore.waitFor(500)) { // may need to wait until another thread is done with indexing
1226 if (Thread.holdsLock(PsiLock.LOCK)) {
1227 break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
1230 if (allDocsProcessed && !hasActiveTransactions()) {
1231 myUpToDateIndices.add(indexId); // safe to set the flag here, because it will be cleared under the WriteAction
1241 private boolean hasActiveTransactions() {
1242 synchronized (myTransactionMap) {
1243 return !myTransactionMap.isEmpty();
1247 private interface DocumentContent {
1249 long getModificationStamp();
1252 private static class AuthenticContent implements DocumentContent {
1253 private final Document myDocument;
1255 private AuthenticContent(final Document document) {
1256 myDocument = document;
1260 public String getText() {
1261 return myDocument.getText();
1265 public long getModificationStamp() {
1266 return myDocument.getModificationStamp();
1270 private static class PsiContent implements DocumentContent {
1271 private final Document myDocument;
1272 private final PsiFile myFile;
1274 private PsiContent(final Document document, final PsiFile file) {
1275 myDocument = document;
1280 public String getText() {
1281 if (myFile.getModificationStamp() != myDocument.getModificationStamp()) {
1282 final ASTNode node = myFile.getNode();
1283 assert node != null;
1284 return node.getText();
1286 return myDocument.getText();
1290 public long getModificationStamp() {
1291 return myFile.getModificationStamp();
1295 // returns false if doc was not indexed because the file does not fit in scope
1296 private boolean indexUnsavedDocument(@NotNull final Document document, @NotNull final ID<?, ?> requestedIndexId, final Project project,
1297 GlobalSearchScope filter, VirtualFile restrictedFile) throws StorageException {
1298 final VirtualFile vFile = myFileDocumentManager.getFile(document);
1299 if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) {
1303 if (restrictedFile != null) {
1304 if(vFile != restrictedFile) {
1308 else if (filter != null && !filter.accept(vFile)) {
1312 final PsiFile dominantContentFile = findDominantPsiForDocument(document, project);
1314 final DocumentContent content;
1315 if (dominantContentFile != null && dominantContentFile.getModificationStamp() != document.getModificationStamp()) {
1316 content = new PsiContent(document, dominantContentFile);
1319 content = new AuthenticContent(document);
1322 final long currentDocStamp = content.getModificationStamp();
1323 if (currentDocStamp != myLastIndexedDocStamps.getAndSet(document, requestedIndexId, currentDocStamp)) {
1324 final Ref<StorageException> exRef = new Ref<StorageException>(null);
1325 ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
1329 final String contentText = content.getText();
1330 if (isTooLarge(vFile, contentText.length())) {
1334 final FileContentImpl newFc = new FileContentImpl(vFile, contentText, vFile.getCharset());
1336 if (dominantContentFile != null) {
1337 dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1338 newFc.putUserData(IndexingDataKeys.PSI_FILE, dominantContentFile);
1341 if (content instanceof AuthenticContent) {
1342 newFc.putUserData(EDITOR_HIGHLIGHTER, EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document));
1345 if (getInputFilter(requestedIndexId).acceptInput(vFile)) {
1346 newFc.putUserData(IndexingDataKeys.PROJECT, project);
1347 final int inputId = Math.abs(getFileId(vFile));
1348 getIndex(requestedIndexId).update(inputId, newFc);
1351 if (dominantContentFile != null) {
1352 dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1355 catch (StorageException e) {
1360 final StorageException storageException = exRef.get();
1361 if (storageException != null) {
1362 throw storageException;
1368 public static final Key<EditorHighlighter> EDITOR_HIGHLIGHTER = new Key<EditorHighlighter>("Editor");
1371 private PsiFile findDominantPsiForDocument(@NotNull Document document, @Nullable Project project) {
1372 synchronized (myTransactionMap) {
1373 PsiFile psiFile = myTransactionMap.get(document);
1374 if (psiFile != null) return psiFile;
1377 return project == null ? null : findLatestKnownPsiForUncomittedDocument(document, project);
1380 private final StorageGuard myStorageLock = new StorageGuard();
1382 private StorageGuard.Holder setDataBufferingEnabled(final boolean enabled) {
1383 final StorageGuard.Holder holder = myStorageLock.enter(enabled);
1384 for (ID<?, ?> indexId : myIndices.keySet()) {
1385 final MapReduceIndex index = (MapReduceIndex)getIndex(indexId);
1386 assert index != null;
1387 final IndexStorage indexStorage = index.getStorage();
1388 ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled);
1393 private void cleanupMemoryStorage() {
1394 myLastIndexedDocStamps.clear();
1395 for (ID<?, ?> indexId : myIndices.keySet()) {
1396 final MapReduceIndex index = (MapReduceIndex)getIndex(indexId);
1397 assert index != null;
1398 final MemoryIndexStorage memStorage = (MemoryIndexStorage)index.getStorage();
1399 index.getWriteLock().lock();
1401 memStorage.clearMemoryMap();
1404 index.getWriteLock().unlock();
1406 memStorage.fireMemoryStorageCleared();
1410 private void dropUnregisteredIndices() {
1411 final Set<String> indicesToDrop = readRegistsredIndexNames();
1412 for (ID<?, ?> key : myIndices.keySet()) {
1413 indicesToDrop.remove(key.toString());
1415 for (String s : indicesToDrop) {
1416 FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s)));
1420 public static void requestRebuild(ID<?, ?> indexId) {
1421 requestRebuild(indexId, new Throwable());
1424 public static void requestRebuild(ID<?, ?> indexId, Throwable throwable) {
1425 cleanupProcessedFlag();
1426 LOG.info("Rebuild requested for index " + indexId, throwable);
1427 ourRebuildStatus.get(indexId).set(REQUIRES_REBUILD);
1430 private <K, V> UpdatableIndex<K, V, FileContent> getIndex(ID<K, V> indexId) {
1431 final Pair<UpdatableIndex<?, ?, FileContent>, InputFilter> pair = myIndices.get(indexId);
1433 assert pair != null : "Index data is absent for index " + indexId;
1435 //noinspection unchecked
1436 return (UpdatableIndex<K,V, FileContent>)pair.getFirst();
1439 private InputFilter getInputFilter(ID<?, ?> indexId) {
1440 final Pair<UpdatableIndex<?, ?, FileContent>, InputFilter> pair = myIndices.get(indexId);
1442 assert pair != null : "Index data is absent for index " + indexId;
1444 return pair.getSecond();
1447 public int getNumberOfPendingInvalidations() {
1448 return myChangedFilesCollector.getNumberOfPendingInvalidations();
1451 public Collection<VirtualFile> getFilesToUpdate(final Project project) {
1452 return ContainerUtil.findAll(myChangedFilesCollector.getAllFilesToUpdate(), new Condition<VirtualFile>() {
1454 public boolean value(VirtualFile virtualFile) {
1455 for (IndexableFileSet set : myIndexableSets) {
1456 final Project proj = myIndexableSetToProjectMap.get(set);
1457 if (proj != null && !proj.equals(project)) {
1458 continue; // skip this set as associated with a different project
1460 if (set.isInSet(virtualFile)) {
1469 public void processRefreshedFile(@NotNull Project project, final com.intellij.ide.caches.FileContent fileContent) {
1470 myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
1471 myChangedFilesCollector.processFileImpl(project, fileContent, false);
1474 public void indexFileContent(@Nullable Project project, com.intellij.ide.caches.FileContent content) {
1475 myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
1476 final VirtualFile file = content.getVirtualFile();
1477 FileContentImpl fc = null;
1479 PsiFile psiFile = null;
1481 for (final ID<?, ?> indexId : myIndices.keySet()) {
1482 if (shouldIndexFile(file, indexId)) {
1484 byte[] currentBytes;
1486 currentBytes = content.getBytes();
1488 catch (IOException e) {
1489 currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY;
1491 fc = new FileContentImpl(file, currentBytes);
1493 psiFile = content.getUserData(IndexingDataKeys.PSI_FILE);
1494 if (psiFile != null) {
1495 psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1496 fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
1498 if (project == null) {
1499 project = ProjectUtil.guessProjectForFile(file);
1501 fc.putUserData(IndexingDataKeys.PROJECT, project);
1505 ProgressManager.checkCanceled();
1506 updateSingleIndex(indexId, file, fc);
1508 catch (ProcessCanceledException e) {
1509 myChangedFilesCollector.scheduleForUpdate(file);
1512 catch (StorageException e) {
1513 requestRebuild(indexId);
1519 if (psiFile != null) {
1520 psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1524 private void updateSingleIndex(final ID<?, ?> indexId, final VirtualFile file, final FileContent currentFC)
1525 throws StorageException {
1526 if (ourRebuildStatus.get(indexId).get() == REQUIRES_REBUILD) {
1527 return; // the index is scheduled for rebuild, no need to update
1531 final StorageGuard.Holder lock = setDataBufferingEnabled(false);
1534 final int inputId = Math.abs(getFileId(file));
1536 final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1537 assert index != null;
1539 final Ref<StorageException> exRef = new Ref<StorageException>(null);
1540 ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
1544 index.update(inputId, currentFC);
1546 catch (StorageException e) {
1551 final StorageException storageException = exRef.get();
1552 if (storageException != null) {
1553 throw storageException;
1555 ApplicationManager.getApplication().runReadAction(new Runnable() {
1558 if (file.isValid()) {
1559 if (currentFC != null) {
1560 IndexingStamp.update(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId));
1563 // mark the file as unindexed
1564 IndexingStamp.update(file, indexId, -1L);
1575 public static int getFileId(final VirtualFile file) {
1576 if (file instanceof VirtualFileWithId) {
1577 return ((VirtualFileWithId)file).getId();
1580 throw new IllegalArgumentException("Virtual file doesn't support id: " + file + ", implementation class: " + file.getClass().getName());
1583 private boolean needsFileContentLoading(ID<?, ?> indexId) {
1584 return !myNotRequiringContentIndices.contains(indexId);
1587 private abstract static class InvalidationTask implements Runnable {
1588 private final VirtualFile mySubj;
1590 protected InvalidationTask(final VirtualFile subj) {
1594 public VirtualFile getSubj() {
1599 private final class ChangedFilesCollector extends VirtualFileAdapter {
1600 private final Set<VirtualFile> myFilesToUpdate = new ConcurrentHashSet<VirtualFile>();
1601 private final Queue<InvalidationTask> myFutureInvalidations = new ConcurrentLinkedQueue<InvalidationTask>();
1603 private final ManagingFS myManagingFS = ManagingFS.getInstance();
1604 // No need to react on movement events since files stay valid, their ids don't change and all associated attributes remain intact.
1607 public void fileCreated(final VirtualFileEvent event) {
1612 public void fileDeleted(final VirtualFileEvent event) {
1613 myFilesToUpdate.remove(event.getFile()); // no need to update it anymore
1617 public void fileCopied(final VirtualFileCopyEvent event) {
1622 public void beforeFileDeletion(final VirtualFileEvent event) {
1623 invalidateIndices(event.getFile(), false);
1627 public void beforeContentsChange(final VirtualFileEvent event) {
1628 invalidateIndices(event.getFile(), true);
1632 public void contentsChanged(final VirtualFileEvent event) {
1637 public void beforePropertyChange(final VirtualFilePropertyEvent event) {
1638 if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
1639 // indexes may depend on file name
1640 final VirtualFile file = event.getFile();
1641 if (!file.isDirectory()) {
1642 // name change may lead to filetype change so the file might become not indexable
1643 // in general case have to 'unindex' the file and index it again if needed after the name has been changed
1644 invalidateIndices(file, false);
1650 public void propertyChanged(final VirtualFilePropertyEvent event) {
1651 if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
1652 // indexes may depend on file name
1653 if (!event.getFile().isDirectory()) {
1659 private void markDirty(final VirtualFileEvent event) {
1660 final VirtualFile eventFile = event.getFile();
1661 cleanProcessedFlag(eventFile);
1662 iterateIndexableFiles(eventFile, new Processor<VirtualFile>() {
1664 public boolean process(final VirtualFile file) {
1665 FileContent fileContent = null;
1666 // handle 'content-less' indices separately
1667 for (ID<?, ?> indexId : myNotRequiringContentIndices) {
1668 if (getInputFilter(indexId).acceptInput(file)) {
1670 if (fileContent == null) {
1671 fileContent = new FileContentImpl(file);
1673 updateSingleIndex(indexId, file, fileContent);
1675 catch (StorageException e) {
1677 requestRebuild(indexId);
1681 // For 'normal indices' schedule the file for update and stop iteration if at least one index accepts it
1682 if (!isTooLarge(file)) {
1683 for (ID<?, ?> indexId : myIndices.keySet()) {
1684 if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) {
1685 scheduleForUpdate(file);
1686 break; // no need to iterate further, as the file is already marked
1694 IndexingStamp.flushCache();
1697 public void scheduleForUpdate(VirtualFile file) {
1698 myFilesToUpdate.add(file);
1701 void invalidateIndices(final VirtualFile file, final boolean markForReindex) {
1702 if (isUnderConfigOrSystem(file)) {
1705 if (file.isDirectory()) {
1706 if (isMock(file) || myManagingFS.wereChildrenAccessed(file)) {
1707 final Iterable<VirtualFile> children = file instanceof NewVirtualFile
1708 ? ((NewVirtualFile)file).iterInDbChildren() : Arrays.asList(file.getChildren());
1709 for (VirtualFile child : children) {
1710 invalidateIndices(child, markForReindex);
1715 cleanProcessedFlag(file);
1716 IndexingStamp.flushCache();
1717 final List<ID<?, ?>> affectedIndices = new ArrayList<ID<?, ?>>(myIndices.size());
1719 for (final ID<?, ?> indexId : myIndices.keySet()) {
1721 if (!needsFileContentLoading(indexId)) {
1722 if (shouldUpdateIndex(file, indexId)) {
1723 updateSingleIndex(indexId, file, null);
1726 else { // the index requires file content
1727 if (shouldUpdateIndex(file, indexId)) {
1728 affectedIndices.add(indexId);
1732 catch (StorageException e) {
1734 requestRebuild(indexId);
1738 if (!affectedIndices.isEmpty()) {
1739 if (markForReindex && !isTooLarge(file)) {
1740 // only mark the file as unindexed, reindex will be done lazily
1741 ApplicationManager.getApplication().runReadAction(new Runnable() {
1744 for (ID<?, ?> indexId : affectedIndices) {
1745 IndexingStamp.update(file, indexId, -2L);
1749 // the file is for sure not a dir and it was previously indexed by at least one index
1750 scheduleForUpdate(file);
1753 myFutureInvalidations.offer(new InvalidationTask(file) {
1756 removeFileDataFromIndices(affectedIndices, file);
1761 if (!markForReindex) {
1762 final boolean removedFromUpdateQueue = myFilesToUpdate.remove(file);// no need to update it anymore
1763 if (removedFromUpdateQueue && affectedIndices.isEmpty()) {
1764 // Currently the file is about to be deleted and previously it was scheduled for update and not processed up to now.
1765 // Because the file was scheduled for update, at the moment of scheduling it was marked as unindexed,
1766 // so, to be on the safe side, we have to schedule data invalidation from all content-requiring indices for this file
1767 myFutureInvalidations.offer(new InvalidationTask(file) {
1770 removeFileDataFromIndices(myRequiringContentIndices, file);
1776 IndexingStamp.flushCache();
1780 private void removeFileDataFromIndices(Collection<ID<?, ?>> affectedIndices, VirtualFile file) {
1781 Throwable unexpectedError = null;
1782 for (ID<?, ?> indexId : affectedIndices) {
1784 updateSingleIndex(indexId, file, null);
1786 catch (StorageException e) {
1788 requestRebuild(indexId);
1790 catch (ProcessCanceledException ignored) {
1792 catch (Throwable e) {
1794 if (unexpectedError == null) {
1795 unexpectedError = e;
1799 IndexingStamp.flushCache();
1800 if (unexpectedError != null) {
1801 LOG.error(unexpectedError);
1805 public int getNumberOfPendingInvalidations() {
1806 return myFutureInvalidations.size();
1809 public void ensureAllInvalidateTasksCompleted() {
1810 final int size = getNumberOfPendingInvalidations();
1814 final ProgressIndicator current = ProgressManager.getInstance().getProgressIndicator();
1815 final ProgressIndicator indicator = current != null ? current : new EmptyProgressIndicator();
1816 indicator.setText("");
1819 InvalidationTask task = myFutureInvalidations.poll();
1824 indicator.setFraction((double)count++ /size);
1825 indicator.setText2(task.getSubj().getPresentableUrl());
1830 private void iterateIndexableFiles(final VirtualFile file, final Processor<VirtualFile> processor) {
1831 if (file.isDirectory()) {
1832 final ContentIterator iterator = new ContentIterator() {
1834 public boolean processFile(final VirtualFile fileOrDir) {
1835 if (!fileOrDir.isDirectory()) {
1836 processor.process(fileOrDir);
1842 for (IndexableFileSet set : myIndexableSets) {
1843 set.iterateIndexableFilesIn(file, iterator);
1847 for (IndexableFileSet set : myIndexableSets) {
1848 if (set.isInSet(file)) {
1849 processor.process(file);
1856 public Collection<VirtualFile> getAllFilesToUpdate() {
1857 if (myFilesToUpdate.isEmpty()) {
1858 return Collections.emptyList();
1860 return new ArrayList<VirtualFile>(myFilesToUpdate);
1863 private final Semaphore myForceUpdateSemaphore = new Semaphore();
1865 private void forceUpdate(@Nullable Project project, @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedTo,
1866 boolean onlyRemoveOutdatedData) {
1867 myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
1868 for (VirtualFile file: getAllFilesToUpdate()) {
1869 if (filter == null || filter.accept(file) || file == restrictedTo) {
1871 myForceUpdateSemaphore.down();
1872 // process only files that can affect result
1873 processFileImpl(project, new com.intellij.ide.caches.FileContent(file), onlyRemoveOutdatedData);
1876 myForceUpdateSemaphore.up();
1881 // If several threads entered the method at the same time and there were files to update,
1882 // all the threads should leave the method synchronously after all the files scheduled for update are reindexed,
1883 // no matter which thread will do reindexing job.
1884 // Thus we ensure that all the threads that entered the method will get the most recent data
1886 while (!myForceUpdateSemaphore.waitFor(500)) { // may need to wait until another thread is done with indexing
1887 if (Thread.holdsLock(PsiLock.LOCK)) {
1888 break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
1893 private void processFileImpl(Project project, final com.intellij.ide.caches.FileContent fileContent, boolean onlyRemoveOutdatedData) {
1894 final VirtualFile file = fileContent.getVirtualFile();
1895 final boolean reallyRemoved = myFilesToUpdate.remove(file);
1896 if (reallyRemoved && file.isValid()) {
1897 if (onlyRemoveOutdatedData) {
1898 // on shutdown there is no need to re-index the file, just remove outdated data from indices
1899 final List<ID<?, ?>> affected = new ArrayList<ID<?,?>>();
1900 for (final ID<?, ?> indexId : myIndices.keySet()) {
1901 if (getInputFilter(indexId).acceptInput(file)) {
1902 affected.add(indexId);
1905 removeFileDataFromIndices(affected, file);
1908 indexFileContent(project, fileContent);
1910 IndexingStamp.flushCache();
1915 private class UnindexedFilesFinder implements CollectingContentIterator {
1916 private final List<VirtualFile> myFiles = new ArrayList<VirtualFile>();
1917 private final ProgressIndicator myProgressIndicator;
1919 private UnindexedFilesFinder() {
1920 myProgressIndicator = ProgressManager.getInstance().getProgressIndicator();
1924 public List<VirtualFile> getFiles() {
1929 public boolean processFile(final VirtualFile file) {
1930 if (!file.isDirectory()) {
1931 if (file instanceof NewVirtualFile && ((NewVirtualFile)file).getFlag(ALREADY_PROCESSED)) {
1935 if (file instanceof VirtualFileWithId) {
1937 FileTypeManagerImpl.cacheFileType(file, file.getFileType());
1939 boolean oldStuff = true;
1940 if (!isTooLarge(file)) {
1941 for (ID<?, ?> indexId : myIndices.keySet()) {
1943 if (needsFileContentLoading(indexId) && shouldIndexFile(file, indexId)) {
1949 catch (RuntimeException e) {
1950 final Throwable cause = e.getCause();
1951 if (cause instanceof IOException || cause instanceof StorageException) {
1953 requestRebuild(indexId);
1961 FileContent fileContent = null;
1962 for (ID<?, ?> indexId : myNotRequiringContentIndices) {
1963 if (shouldIndexFile(file, indexId)) {
1966 if (fileContent == null) {
1967 fileContent = new FileContentImpl(file);
1969 updateSingleIndex(indexId, file, fileContent);
1971 catch (StorageException e) {
1973 requestRebuild(indexId);
1977 IndexingStamp.flushCache();
1979 if (oldStuff && file instanceof NewVirtualFile) {
1980 ((NewVirtualFile)file).setFlag(ALREADY_PROCESSED, true);
1984 FileTypeManagerImpl.cacheFileType(file, null);
1989 if (myProgressIndicator != null) {
1990 myProgressIndicator.setText("Scanning files to index");
1991 myProgressIndicator.setText2(file.getPresentableUrl());
1998 private boolean shouldUpdateIndex(final VirtualFile file, final ID<?, ?> indexId) {
1999 return getInputFilter(indexId).acceptInput(file) &&
2000 (isMock(file) || IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)));
2003 private boolean shouldIndexFile(final VirtualFile file, final ID<?, ?> indexId) {
2004 return getInputFilter(indexId).acceptInput(file) &&
2005 (isMock(file) || !IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)));
2008 private boolean isUnderConfigOrSystem(VirtualFile file) {
2009 final String filePath = file.getPath();
2010 return myConfigPath != null && FileUtil.startsWith(filePath, myConfigPath) ||
2011 mySystemPath != null && FileUtil.startsWith(filePath, mySystemPath);
2014 private static boolean isMock(final VirtualFile file) {
2015 return !(file instanceof NewVirtualFile);
2018 private boolean isTooLarge(VirtualFile file) {
2019 if (SingleRootFileViewProvider.isTooLarge(file)) {
2020 final FileType type = file.getFileType();
2021 return !myNoLimitCheckTypes.contains(type);
2026 private boolean isTooLarge(VirtualFile file, long contentSize) {
2027 if (SingleRootFileViewProvider.isTooLarge(file, contentSize)) {
2028 final FileType type = file.getFileType();
2029 return !myNoLimitCheckTypes.contains(type);
2034 public CollectingContentIterator createContentIterator() {
2035 return new UnindexedFilesFinder();
2038 public void registerIndexableSet(IndexableFileSet set, @Nullable Project project) {
2039 myIndexableSets.add(set);
2040 myIndexableSetToProjectMap.put(set, project);
2043 public void removeIndexableSet(IndexableFileSet set) {
2044 myChangedFilesCollector.forceUpdate(null, null, null, true);
2045 myIndexableSets.remove(set);
2046 myIndexableSetToProjectMap.remove(set);
2050 private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) {
2051 return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc);
2054 private static class IndexableFilesFilter implements InputFilter {
2055 private final InputFilter myDelegate;
2057 private IndexableFilesFilter(InputFilter delegate) {
2058 myDelegate = delegate;
2062 public boolean acceptInput(final VirtualFile file) {
2063 return file instanceof VirtualFileWithId && myDelegate.acceptInput(file);
2067 private static void cleanupProcessedFlag() {
2068 final VirtualFile[] roots = ManagingFS.getInstance().getRoots();
2069 for (VirtualFile root : roots) {
2070 cleanProcessedFlag(root);
2074 private static void cleanProcessedFlag(final VirtualFile file) {
2075 if (!(file instanceof NewVirtualFile)) return;
2077 final NewVirtualFile nvf = (NewVirtualFile)file;
2078 if (file.isDirectory()) {
2079 for (VirtualFile child : nvf.getCachedChildren()) {
2080 cleanProcessedFlag(child);
2084 nvf.setFlag(ALREADY_PROCESSED, false);
2088 public static void iterateIndexableFiles(final ContentIterator processor, Project project, ProgressIndicator indicator) {
2089 if (project.isDisposed()) {
2092 final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
2093 // iterate project content
2094 projectFileIndex.iterateContent(processor);
2096 if (project.isDisposed()) {
2100 Set<VirtualFile> visitedRoots = new HashSet<VirtualFile>();
2101 for (IndexedRootsProvider provider : Extensions.getExtensions(IndexedRootsProvider.EP_NAME)) {
2102 //important not to depend on project here, to support per-project background reindex
2103 // each client gives a project to FileBasedIndex
2104 if (project.isDisposed()) {
2107 for (VirtualFile root : IndexableSetContributor.getRootsToIndex(provider)) {
2108 if (visitedRoots.add(root)) {
2109 iterateRecursively(root, processor, indicator);
2112 for (VirtualFile root : IndexableSetContributor.getProjectRootsToIndex(provider, project)) {
2113 if (visitedRoots.add(root)) {
2114 iterateRecursively(root, processor, indicator);
2119 if (project.isDisposed()) {
2122 // iterate associated libraries
2123 for (Module module : ModuleManager.getInstance(project).getModules()) {
2124 if (module.isDisposed()) {
2127 OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries();
2128 for (OrderEntry orderEntry : orderEntries) {
2129 if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) {
2130 if (orderEntry.isValid()) {
2131 final VirtualFile[] libSources = orderEntry.getFiles(OrderRootType.SOURCES);
2132 final VirtualFile[] libClasses = orderEntry.getFiles(OrderRootType.CLASSES);
2133 for (VirtualFile[] roots : new VirtualFile[][]{libSources, libClasses}) {
2134 for (VirtualFile root : roots) {
2135 if (visitedRoots.add(root)) {
2136 iterateRecursively(root, processor, indicator);
2146 private static void iterateRecursively(@Nullable final VirtualFile root, final ContentIterator processor, ProgressIndicator indicator) {
2148 if (indicator != null) {
2149 indicator.checkCanceled();
2150 indicator.setText2(root.getPresentableUrl());
2153 if (root.isDirectory()) {
2154 for (VirtualFile file : root.getChildren()) {
2155 if (file.isDirectory()) {
2156 iterateRecursively(file, processor, indicator);
2159 processor.processFile(file);
2164 processor.processFile(root);
2169 private static class StorageGuard {
2170 private int myHolds = 0;
2172 public interface Holder {
2176 private final Holder myTrueHolder = new Holder() {
2178 public void leave() {
2179 StorageGuard.this.leave(true);
2182 private final Holder myFalseHolder = new Holder() {
2184 public void leave() {
2185 StorageGuard.this.leave(false);
2189 public synchronized Holder enter(boolean mode) {
2191 while (myHolds < 0) {
2195 catch (InterruptedException ignored) {
2199 return myTrueHolder;
2202 while (myHolds > 0) {
2206 catch (InterruptedException ignored) {
2210 return myFalseHolder;
2214 private synchronized void leave(boolean mode) {
2215 myHolds += mode? -1 : 1;