997c82edbb18bbe8578a082384c0f88dfe612baa
[idea/community.git] / platform / lang-impl / src / com / intellij / util / indexing / FileBasedIndexImpl.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.util.indexing;
3
4 import com.google.common.annotations.VisibleForTesting;
5 import com.intellij.AppTopics;
6 import com.intellij.ide.AppLifecycleListener;
7 import com.intellij.ide.plugins.DynamicPluginListener;
8 import com.intellij.ide.startup.ServiceNotReadyException;
9 import com.intellij.model.ModelBranch;
10 import com.intellij.openapi.actionSystem.ex.ActionUtil;
11 import com.intellij.openapi.application.AppUIExecutor;
12 import com.intellij.openapi.application.Application;
13 import com.intellij.openapi.application.ApplicationManager;
14 import com.intellij.openapi.application.ReadAction;
15 import com.intellij.openapi.diagnostic.Logger;
16 import com.intellij.openapi.editor.Document;
17 import com.intellij.openapi.editor.impl.EditorHighlighterCache;
18 import com.intellij.openapi.extensions.ExtensionPointListener;
19 import com.intellij.openapi.extensions.PluginDescriptor;
20 import com.intellij.openapi.fileEditor.FileDocumentManager;
21 import com.intellij.openapi.fileEditor.FileDocumentManagerListener;
22 import com.intellij.openapi.fileTypes.FileType;
23 import com.intellij.openapi.fileTypes.FileTypeManager;
24 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
25 import com.intellij.openapi.progress.ProcessCanceledException;
26 import com.intellij.openapi.progress.ProgressIndicator;
27 import com.intellij.openapi.progress.ProgressManager;
28 import com.intellij.openapi.progress.Task;
29 import com.intellij.openapi.project.*;
30 import com.intellij.openapi.util.*;
31 import com.intellij.openapi.util.io.FileUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.openapi.vfs.VirtualFileWithId;
34 import com.intellij.openapi.vfs.newvfs.AsyncEventSupport;
35 import com.intellij.openapi.vfs.newvfs.ManagingFS;
36 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
37 import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry;
38 import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
39 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
40 import com.intellij.psi.*;
41 import com.intellij.psi.impl.PsiDocumentTransactionListener;
42 import com.intellij.psi.impl.PsiManagerImpl;
43 import com.intellij.psi.impl.PsiTreeChangeEventImpl;
44 import com.intellij.psi.impl.cache.impl.id.PlatformIdTableBuilding;
45 import com.intellij.psi.impl.source.PsiFileImpl;
46 import com.intellij.psi.search.GlobalSearchScope;
47 import com.intellij.psi.stubs.SerializationManagerEx;
48 import com.intellij.psi.util.CachedValueProvider;
49 import com.intellij.psi.util.CachedValuesManager;
50 import com.intellij.util.*;
51 import com.intellij.util.containers.ContainerUtil;
52 import com.intellij.util.containers.FactoryMap;
53 import com.intellij.util.gist.GistManager;
54 import com.intellij.util.indexing.contentQueue.CachedFileContent;
55 import com.intellij.util.indexing.diagnostic.FileIndexingStatistics;
56 import com.intellij.util.indexing.impl.MapReduceIndex;
57 import com.intellij.util.indexing.impl.storage.TransientChangesIndexStorage;
58 import com.intellij.util.indexing.impl.storage.VfsAwareMapIndexStorage;
59 import com.intellij.util.indexing.impl.storage.VfsAwareMapReduceIndex;
60 import com.intellij.util.indexing.memory.InMemoryIndexStorage;
61 import com.intellij.util.indexing.snapshot.SnapshotHashEnumeratorService;
62 import com.intellij.util.indexing.snapshot.SnapshotInputMappings;
63 import com.intellij.util.indexing.snapshot.SnapshotSingleValueIndexStorage;
64 import com.intellij.util.io.storage.HeavyProcessLatch;
65 import com.intellij.util.messages.SimpleMessageBusConnection;
66 import it.unimi.dsi.fastutil.ints.IntArrayList;
67 import org.jetbrains.annotations.ApiStatus;
68 import org.jetbrains.annotations.NotNull;
69 import org.jetbrains.annotations.Nullable;
70 import org.jetbrains.annotations.TestOnly;
71
72 import java.io.File;
73 import java.io.IOException;
74 import java.lang.ref.SoftReference;
75 import java.lang.ref.WeakReference;
76 import java.util.*;
77 import java.util.concurrent.ScheduledFuture;
78 import java.util.concurrent.atomic.AtomicInteger;
79 import java.util.concurrent.locks.Lock;
80 import java.util.concurrent.locks.ReadWriteLock;
81 import java.util.concurrent.locks.ReentrantLock;
82 import java.util.concurrent.locks.ReentrantReadWriteLock;
83 import java.util.function.IntPredicate;
84 import java.util.function.Predicate;
85 import java.util.stream.Collectors;
86 import java.util.stream.Stream;
87
88 public final class FileBasedIndexImpl extends FileBasedIndexEx {
89   private static final ThreadLocal<VirtualFile> ourIndexedFile = new ThreadLocal<>();
90   private static final ThreadLocal<VirtualFile> ourFileToBeIndexed = new ThreadLocal<>();
91   public static final Logger LOG = Logger.getInstance(FileBasedIndexImpl.class);
92
93   private volatile RegisteredIndexes myRegisteredIndexes;
94
95   private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap();
96
97   // findExtensionOrFail is thread safe
98   private final NotNullLazyValue<ChangedFilesCollector> myChangedFilesCollector = NotNullLazyValue.createValue(()
99            -> AsyncEventSupport.EP_NAME.findExtensionOrFail(ChangedFilesCollector.class));
100
101   private final List<IndexableFileSet> myIndexableSets = ContainerUtil.createLockFreeCopyOnWriteList();
102   private final Map<IndexableFileSet, Project> myIndexableSetToProjectMap = new HashMap<>();
103
104   private final SimpleMessageBusConnection myConnection;
105   private final FileDocumentManager myFileDocumentManager;
106
107   private final Set<ID<?, ?>> myUpToDateIndicesForUnsavedOrTransactedDocuments = ContainerUtil.newConcurrentSet();
108   private volatile SmartFMap<Document, PsiFile> myTransactionMap = SmartFMap.emptyMap();
109
110   private final boolean myIsUnitTestMode;
111
112   @Nullable
113   private Runnable myShutDownTask;
114   @Nullable
115   private ScheduledFuture<?> myFlushingFuture;
116
117   private final AtomicInteger myLocalModCount = new AtomicInteger();
118   private final AtomicInteger myFilesModCount = new AtomicInteger();
119   private final Set<Project> myProjectsBeingUpdated = ContainerUtil.newConcurrentSet();
120
121   private final Lock myReadLock;
122   final Lock myWriteLock;
123
124   private IndexConfiguration getState() {
125     return myRegisteredIndexes.getConfigurationState();
126   }
127
128   void dropRegisteredIndexes() {
129     ScheduledFuture<?> flushingFuture = myFlushingFuture;
130     LOG.assertTrue(flushingFuture == null || flushingFuture.isCancelled() || flushingFuture.isDone());
131     LOG.assertTrue(myUpToDateIndicesForUnsavedOrTransactedDocuments.isEmpty());
132     LOG.assertTrue(myProjectsBeingUpdated.isEmpty());
133     LOG.assertTrue(!getChangedFilesCollector().isUpdateInProgress());
134     LOG.assertTrue(myTransactionMap.isEmpty());
135
136     myRegisteredIndexes = null;
137   }
138
139   public FileBasedIndexImpl() {
140     ReadWriteLock lock = new ReentrantReadWriteLock();
141     myReadLock = lock.readLock();
142     myWriteLock = lock.writeLock();
143
144     myFileDocumentManager = FileDocumentManager.getInstance();
145     myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
146
147     SimpleMessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().simpleConnect();
148     connection.subscribe(DynamicPluginListener.TOPIC, new FileBasedIndexPluginListener(this));
149
150     connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() {
151       @Override
152       public void transactionStarted(@NotNull final Document doc, @NotNull final PsiFile file) {
153         myTransactionMap = myTransactionMap.plus(doc, file);
154         clearUpToDateIndexesForUnsavedOrTransactedDocs();
155       }
156
157       @Override
158       public void transactionCompleted(@NotNull final Document doc, @NotNull final PsiFile file) {
159         myTransactionMap = myTransactionMap.minus(doc);
160       }
161     });
162
163     connection.subscribe(FileTypeManager.TOPIC, new FileBasedIndexFileTypeListener());
164
165     connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerListener() {
166       @Override
167       public void fileContentReloaded(@NotNull VirtualFile file, @NotNull Document document) {
168         cleanupMemoryStorage(true);
169       }
170
171       @Override
172       public void unsavedDocumentsDropped() {
173         cleanupMemoryStorage(false);
174       }
175     });
176
177     connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener() {
178       @Override
179       public void appWillBeClosed(boolean isRestart) {
180         if (!myRegisteredIndexes.areIndexesReady()) {
181           new Task.Modal(null, IndexingBundle.message("indexes.preparing.to.shutdown.message"), false) {
182             @Override
183             public void run(@NotNull ProgressIndicator indicator) {
184               myRegisteredIndexes.waitUntilAllIndicesAreInitialized();
185             }
186           }.queue();
187         }
188       }
189     });
190
191     myConnection = connection;
192
193     FileBasedIndexExtension.EXTENSION_POINT_NAME.addExtensionPointListener(new ExtensionPointListener<FileBasedIndexExtension<?, ?>>() {
194       @Override
195       public void extensionRemoved(@NotNull FileBasedIndexExtension<?, ?> extension, @NotNull PluginDescriptor pluginDescriptor) {
196         ID.unloadId(extension.getName());
197       }
198     }, null);
199
200     initComponent();
201   }
202
203   void scheduleFullIndexesRescan(@NotNull Collection<ID<?, ?>> indexesToRebuild, @NotNull String reason) {
204     cleanupProcessedFlag();
205     doClearIndices(id -> indexesToRebuild.contains(id));
206     scheduleIndexRebuild(reason);
207   }
208
209   @VisibleForTesting
210   void doClearIndices(@NotNull Predicate<? super ID<?, ?>> filter) {
211     try {
212       waitUntilIndicesAreInitialized();
213     }
214     catch (ProcessCanceledException e) {
215       // will be rebuilt on re-scan
216       return;
217     }
218     IndexingStamp.flushCaches();
219
220     List<ID<?, ?>> clearedIndexes = new ArrayList<>();
221     List<ID<?, ?>> survivedIndexes = new ArrayList<>();
222     for (ID<?, ?> indexId : getState().getIndexIDs()) {
223       if (filter.test(indexId)) {
224         try {
225           clearIndex(indexId);
226         } catch (StorageException e) {
227           LOG.info(e);
228         } catch (Exception e) {
229           LOG.error(e);
230         }
231         clearedIndexes.add(indexId);
232       } else {
233         survivedIndexes.add(indexId);
234       }
235     }
236
237     LOG.info("indexes cleared: " + clearedIndexes.stream().map(id -> id.getName()).collect(Collectors.joining(", ")) + "\n" +
238              "survived indexes: " + survivedIndexes.stream().map(id -> id.getName()).collect(Collectors.joining(", ")));
239   }
240
241   boolean processChangedFiles(@NotNull Project project, @NotNull Processor<? super VirtualFile> processor) {
242     // avoid missing files when events are processed concurrently
243     return Stream.concat(getChangedFilesCollector().getEventMerger().getChangedFiles(),
244                          getChangedFilesCollector().getFilesToUpdate())
245       .filter(filesToBeIndexedForProjectCondition(project))
246       .distinct()
247       .mapToInt(f -> processor.process(f) ? 1 : 0)
248       .allMatch(success -> success == 1);
249   }
250
251   RegisteredIndexes getRegisteredIndexes() {
252     return myRegisteredIndexes;
253   }
254
255   void setUpShutDownTask() {
256     myShutDownTask = new MyShutDownTask();
257     ShutDownTracker.getInstance().registerShutdownTask(myShutDownTask);
258   }
259
260   @ApiStatus.Internal
261   public void dumpIndexStatistics() {
262     IndexConfiguration state = getRegisteredIndexes().getState();
263     for (ID<?, ?> id : state.getIndexIDs()) {
264       state.getIndex(id).dumpStatistics();
265     }
266   }
267
268   static class MyShutDownTask implements Runnable {
269     @Override
270     public void run() {
271       FileBasedIndex fileBasedIndex = FileBasedIndex.getInstance();
272       if (fileBasedIndex instanceof FileBasedIndexImpl) {
273         ((FileBasedIndexImpl)fileBasedIndex).performShutdown(false);
274       }
275     }
276   }
277
278   public static boolean isProjectOrWorkspaceFile(@NotNull VirtualFile file, @Nullable FileType fileType) {
279     return ProjectCoreUtil.isProjectOrWorkspaceFile(file, fileType);
280   }
281
282   static boolean belongsToScope(VirtualFile file, VirtualFile restrictedTo, GlobalSearchScope filter) {
283     if (!(file instanceof VirtualFileWithId) || !file.isValid()) {
284       return false;
285     }
286
287     return (restrictedTo == null || Comparing.equal(file, restrictedTo)) &&
288            (filter == null || restrictedTo != null || filter.accept(file));
289   }
290
291   @Override
292   public void requestReindex(@NotNull VirtualFile file) {
293     requestReindex(file, true);
294   }
295
296   @ApiStatus.Internal
297   public void requestReindex(@NotNull VirtualFile file, boolean forceRebuildRequest) {
298     GistManager.getInstance().invalidateData(file);
299     // todo: this is the same vfs event handling sequence that is produces after events of FileContentUtilCore.reparseFiles
300     // but it is more costly than current code, see IDEA-192192
301     //myChangedFilesCollector.invalidateIndicesRecursively(file, false);
302     //myChangedFilesCollector.buildIndicesForFileRecursively(file, false);
303     ChangedFilesCollector changedFilesCollector = getChangedFilesCollector();
304     changedFilesCollector.invalidateIndicesRecursively(file, true, forceRebuildRequest, changedFilesCollector.getEventMerger());
305     if (myRegisteredIndexes.isInitialized()) {
306       changedFilesCollector.ensureUpToDateAsync();
307     }
308   }
309
310   void initComponent() {
311     LOG.assertTrue(myRegisteredIndexes == null);
312     myStorageBufferingHandler.resetState();
313     myRegisteredIndexes = new RegisteredIndexes(myFileDocumentManager, this);
314   }
315
316   @Override
317   public void waitUntilIndicesAreInitialized() {
318     if (myRegisteredIndexes == null) {
319       // interrupt all calculation while plugin reload
320       throw new ProcessCanceledException();
321     }
322     myRegisteredIndexes.waitUntilIndicesAreInitialized();
323   }
324
325   static <K, V> void registerIndexer(@NotNull final FileBasedIndexExtension<K, V> extension, @NotNull IndexConfiguration state,
326                                      @NotNull IndexVersionRegistrationSink registrationStatusSink) throws IOException {
327     ID<K, V> name = extension.getName();
328     int version = getIndexExtensionVersion(extension);
329
330     final File versionFile = IndexInfrastructure.getVersionFile(name);
331
332     IndexingStamp.IndexVersionDiff diff = IndexingStamp.versionDiffers(name, version);
333     registrationStatusSink.setIndexVersionDiff(name, diff);
334     if (diff != IndexingStamp.IndexVersionDiff.UP_TO_DATE) {
335       final boolean versionFileExisted = versionFile.exists();
336
337       if (extension.hasSnapshotMapping() && versionFileExisted) {
338         FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name));
339       }
340       File rootDir = IndexInfrastructure.getIndexRootDir(name);
341       if (versionFileExisted) FileUtil.deleteWithRenaming(rootDir);
342       IndexingStamp.rewriteVersion(name, version);
343
344       try {
345         if (versionFileExisted) {
346           for (FileBasedIndexInfrastructureExtension ex : FileBasedIndexInfrastructureExtension.EP_NAME.getExtensionList()) {
347             ex.onFileBasedIndexVersionChanged(name);
348           }
349         }
350       } catch (Exception e) {
351         LOG.error(e);
352       }
353     }
354
355     initIndexStorage(extension, version, state, registrationStatusSink);
356   }
357
358   private static <K, V> void initIndexStorage(@NotNull FileBasedIndexExtension<K, V> extension,
359                                               int version,
360                                               @NotNull IndexConfiguration state,
361                                               @NotNull IndexVersionRegistrationSink registrationStatusSink)
362     throws IOException {
363     VfsAwareIndexStorage<K, V> storage = null;
364     final ID<K, V> name = extension.getName();
365     boolean contentHashesEnumeratorOk = false;
366
367     final InputFilter inputFilter = extension.getInputFilter();
368     final Set<FileType> addedTypes;
369     if (inputFilter instanceof FileBasedIndex.FileTypeSpecificInputFilter) {
370       addedTypes = new HashSet<>();
371       ((FileBasedIndex.FileTypeSpecificInputFilter)inputFilter).registerFileTypesUsedForIndexing(type -> {
372         if (type != null) addedTypes.add(type);
373       });
374     }
375     else {
376       addedTypes = null;
377     }
378
379     for (int attempt = 0; attempt < 2; attempt++) {
380       try {
381         if (VfsAwareMapReduceIndex.hasSnapshotMapping(extension)) {
382           contentHashesEnumeratorOk = SnapshotHashEnumeratorService.getInstance().initialize();
383           if (!contentHashesEnumeratorOk) {
384             throw new IOException("content hash enumerator will be forcibly clean");
385           }
386         }
387
388         storage = createIndexStorage(extension);
389
390         UpdatableIndex<K, V, FileContent> index = createIndex(extension, new TransientChangesIndexStorage<>(storage, name));
391
392         for (FileBasedIndexInfrastructureExtension infrastructureExtension : FileBasedIndexInfrastructureExtension.EP_NAME.getExtensionList()) {
393           UpdatableIndex<K, V, FileContent> intermediateIndex = infrastructureExtension.combineIndex(extension, index);
394           if (intermediateIndex != null) {
395             index = intermediateIndex;
396           }
397         }
398
399         state.registerIndex(name,
400                             index,
401                             file -> file instanceof VirtualFileWithId && inputFilter.acceptInput(file) &&
402                                   !GlobalIndexFilter.isExcludedFromIndexViaFilters(file, name),
403                             version + GlobalIndexFilter.getFiltersVersion(name),
404                             addedTypes);
405         break;
406       }
407       catch (Exception e) {
408         if (ApplicationManager.getApplication().isUnitTestMode()) {
409           LOG.error(e);
410         }
411         else {
412           LOG.info(e);
413         }
414         boolean instantiatedStorage = storage != null;
415         try {
416           if (storage != null) storage.close();
417           storage = null;
418         }
419         catch (Exception ignored) {
420         }
421
422         FileUtil.deleteWithRenaming(IndexInfrastructure.getIndexRootDir(name));
423
424         if (extension.hasSnapshotMapping() && (!contentHashesEnumeratorOk || instantiatedStorage)) {
425           FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name)); // todo there is possibility of corruption of storage and content hashes
426         }
427         registrationStatusSink.setIndexVersionDiff(name, new IndexingStamp.IndexVersionDiff.CorruptedRebuild(version));
428         IndexingStamp.rewriteVersion(name, version);
429       }
430     }
431   }
432
433   @NotNull
434   private static <K, V> VfsAwareIndexStorage<K, V> createIndexStorage(FileBasedIndexExtension<K, V> extension) throws IOException {
435     if (USE_IN_MEMORY_INDEX) {
436       return new InMemoryIndexStorage<>();
437     }
438     boolean createSnapshotStorage = VfsAwareMapReduceIndex.hasSnapshotMapping(extension) && extension instanceof SingleEntryFileBasedIndexExtension;
439     return createSnapshotStorage ? new SnapshotSingleValueIndexStorage<>(extension.getCacheSize()) : new VfsAwareMapIndexStorage<>(
440       IndexInfrastructure.getStorageFile(extension.getName()).toPath(),
441       extension.getKeyDescriptor(),
442       extension.getValueExternalizer(),
443       extension.getCacheSize(),
444       extension.keyIsUniqueForIndexedFile(),
445       extension.traceKeyHashToVirtualFileMapping()
446     );
447   }
448
449   @NotNull
450   private static <K, V> UpdatableIndex<K, V, FileContent> createIndex(@NotNull final FileBasedIndexExtension<K, V> extension,
451                                                                       @NotNull final TransientChangesIndexStorage<K, V> storage)
452     throws StorageException, IOException {
453     return extension instanceof CustomImplementationFileBasedIndexExtension
454            ? ((CustomImplementationFileBasedIndexExtension<K, V>)extension).createIndexImplementation(extension, storage)
455            : new VfsAwareMapReduceIndex<>(extension, storage);
456   }
457
458   void performShutdown(boolean keepConnection) {
459     RegisteredIndexes registeredIndexes = myRegisteredIndexes;
460     if (registeredIndexes == null || !registeredIndexes.performShutdown()) {
461       return; // already shut down
462     }
463
464     registeredIndexes.waitUntilAllIndicesAreInitialized();
465     try {
466       if (myShutDownTask != null) {
467         ShutDownTracker.getInstance().unregisterShutdownTask(myShutDownTask);
468       }
469       if (myFlushingFuture != null) {
470         myFlushingFuture.cancel(false);
471         myFlushingFuture = null;
472       }
473     }
474     finally {
475       LOG.info("START INDEX SHUTDOWN");
476       try {
477         PersistentIndicesConfiguration.saveConfiguration();
478
479         for (VirtualFile file : getChangedFilesCollector().getAllPossibleFilesToUpdate()) {
480           int fileId = getIdMaskingNonIdBasedFile(file);
481           if (file.isValid()) {
482             dropNontrivialIndexedStates(fileId);
483           }
484           else {
485             removeDataFromIndicesForFile(Math.abs(fileId), file);
486           }
487         }
488         getChangedFilesCollector().clearFilesToUpdate();
489
490         IndexingStamp.flushCaches();
491
492         IndexConfiguration state = getState();
493         for (ID<?, ?> indexId : state.getIndexIDs()) {
494           try {
495             final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
496             assert index != null;
497             if (!RebuildStatus.isOk(indexId)) {
498               index.clear(); // if the index was scheduled for rebuild, only clean it
499             }
500             index.dispose();
501           } catch (Throwable throwable) {
502             LOG.info("Problem disposing " + indexId, throwable);
503           }
504         }
505
506         FileBasedIndexInfrastructureExtension.EP_NAME.extensions().forEach(ex -> ex.shutdown());
507         SnapshotHashEnumeratorService.getInstance().close();
508         if (!keepConnection) {
509           myConnection.disconnect();
510         }
511       }
512       catch (Throwable e) {
513         LOG.error("Problems during index shutdown", e);
514       }
515       LOG.info("END INDEX SHUTDOWN");
516     }
517   }
518
519   private void removeDataFromIndicesForFile(int fileId, VirtualFile file) {
520     VirtualFile originalFile = file instanceof DeletedVirtualFileStub ? ((DeletedVirtualFileStub)file).getOriginalFile() : file;
521     final List<ID<?, ?>> states = IndexingStamp.getNontrivialFileIndexedStates(fileId);
522
523     if (!states.isEmpty()) {
524       ProgressManager.getInstance().executeNonCancelableSection(() -> removeFileDataFromIndices(states, fileId, originalFile));
525     }
526   }
527
528   private void removeFileDataFromIndices(@NotNull Collection<? extends ID<?, ?>> affectedIndices, int inputId, VirtualFile file) {
529     // document diff can depend on previous value that will be removed
530     removeTransientFileDataFromIndices(affectedIndices, inputId, file);
531
532     Throwable unexpectedError = null;
533     for (ID<?, ?> indexId : affectedIndices) {
534       try {
535         updateSingleIndex(indexId, null, inputId, null);
536       }
537       catch (ProcessCanceledException pce) {
538         LOG.error(pce);
539       }
540       catch (Throwable e) {
541         LOG.info(e);
542         if (unexpectedError == null) {
543           unexpectedError = e;
544         }
545       }
546     }
547     IndexingStamp.flushCache(inputId);
548
549     if (unexpectedError != null) {
550       LOG.error(unexpectedError);
551     }
552   }
553
554   private void removeTransientFileDataFromIndices(Collection<? extends ID<?, ?>> indices, int inputId, VirtualFile file) {
555     for (ID<?, ?> indexId : indices) {
556       final UpdatableIndex<?, ?, FileContent> index = myRegisteredIndexes.getState().getIndex(indexId);
557       if (index == null) {
558         throw new AssertionError("index '" + indexId.getName() + "' can't be found among registered indexes: " + myRegisteredIndexes.getState().getIndexIDs());
559       }
560       index.removeTransientDataForFile(inputId);
561     }
562
563     Document document = myFileDocumentManager.getCachedDocument(file);
564     if (document != null) {
565       myLastIndexedDocStamps.clearForDocument(document);
566       document.putUserData(ourFileContentKey, null);
567     }
568
569     clearUpToDateIndexesForUnsavedOrTransactedDocs();
570   }
571
572   private void flushAllIndices(final long modCount) {
573     if (HeavyProcessLatch.INSTANCE.isRunning()) {
574       return;
575     }
576     IndexingStamp.flushCaches();
577     IndexConfiguration state = getState();
578     for (ID<?, ?> indexId : new ArrayList<>(state.getIndexIDs())) {
579       if (HeavyProcessLatch.INSTANCE.isRunning() || modCount != myLocalModCount.get()) {
580         return; // do not interfere with 'main' jobs
581       }
582       try {
583         final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
584         if (index != null) {
585           index.flush();
586         }
587       }
588       catch (Throwable e) {
589         requestRebuild(indexId, e);
590       }
591     }
592
593     SnapshotHashEnumeratorService.getInstance().flush();
594   }
595
596   private static final ThreadLocal<Integer> myUpToDateCheckState = new ThreadLocal<>();
597
598   public static <T,E extends Throwable> T disableUpToDateCheckIn(@NotNull ThrowableComputable<T, E> runnable) throws E {
599     disableUpToDateCheckForCurrentThread();
600     try {
601       return runnable.compute();
602     }
603     finally {
604       enableUpToDateCheckForCurrentThread();
605     }
606   }
607   private static void disableUpToDateCheckForCurrentThread() {
608     final Integer currentValue = myUpToDateCheckState.get();
609     myUpToDateCheckState.set(currentValue == null ? 1 : currentValue.intValue() + 1);
610   }
611
612   private static void enableUpToDateCheckForCurrentThread() {
613     final Integer currentValue = myUpToDateCheckState.get();
614     if (currentValue != null) {
615       final int newValue = currentValue.intValue() - 1;
616       if (newValue != 0) {
617         myUpToDateCheckState.set(newValue);
618       }
619       else {
620         myUpToDateCheckState.remove();
621       }
622     }
623   }
624
625   static boolean isUpToDateCheckEnabled() {
626     final Integer value = myUpToDateCheckState.get();
627     return value == null || value.intValue() == 0;
628   }
629
630   private final ThreadLocal<Boolean> myReentrancyGuard = ThreadLocal.withInitial(() -> Boolean.FALSE);
631
632   @Override
633   public <K> boolean ensureUpToDate(@NotNull final ID<K, ?> indexId,
634                                     @Nullable Project project,
635                                     @Nullable GlobalSearchScope filter,
636                                     @Nullable VirtualFile restrictedFile) {
637     ProgressManager.checkCanceled();
638     getChangedFilesCollector().ensureUpToDate();
639     ApplicationManager.getApplication().assertReadAccessAllowed();
640
641     NoAccessDuringPsiEvents.checkCallContext(indexId);
642
643     if (!needsFileContentLoading(indexId)) {
644       return true; //indexed eagerly in foreground while building unindexed file list
645     }
646     if (filter == GlobalSearchScope.EMPTY_SCOPE) {
647       return false;
648     }
649     if (ActionUtil.isDumbMode(project) && getCurrentDumbModeAccessType_NoDumbChecks() == null) {
650       handleDumbMode(project);
651     }
652
653     if (myReentrancyGuard.get().booleanValue()) {
654       //assert false : "ensureUpToDate() is not reentrant!";
655       return true;
656     }
657     myReentrancyGuard.set(Boolean.TRUE);
658
659     try {
660       if (isUpToDateCheckEnabled()) {
661         try {
662           if (!RebuildStatus.isOk(indexId)) {
663             if (getCurrentDumbModeAccessType_NoDumbChecks() == null) {
664               throw new ServiceNotReadyException();
665             }
666             return false;
667           }
668           if (!ActionUtil.isDumbMode(project) || getCurrentDumbModeAccessType_NoDumbChecks() == null) {
669             forceUpdate(project, filter, restrictedFile);
670           }
671           if (!areUnsavedDocumentsIndexed(indexId)) { // todo: check scope ?
672             indexUnsavedDocuments(indexId, project, filter, restrictedFile);
673           }
674         }
675         catch (RuntimeException e) {
676           final Throwable cause = e.getCause();
677           if (cause instanceof StorageException || cause instanceof IOException) {
678             scheduleRebuild(indexId, e);
679           }
680           else {
681             throw e;
682           }
683         }
684       }
685     }
686     finally {
687       myReentrancyGuard.set(Boolean.FALSE);
688     }
689     return true;
690   }
691
692   private boolean areUnsavedDocumentsIndexed(@NotNull ID<?, ?> indexId) {
693     return myUpToDateIndicesForUnsavedOrTransactedDocuments.contains(indexId);
694   }
695
696   private static void handleDumbMode(@Nullable Project project) throws IndexNotReadyException {
697     ProgressManager.checkCanceled();
698     throw IndexNotReadyException.create(project == null ? null : DumbServiceImpl.getInstance(project).getDumbModeStartTrace());
699   }
700
701   private static final Key<SoftReference<ProjectIndexableFilesFilter>> ourProjectFilesSetKey = Key.create("projectFiles");
702
703   @TestOnly
704   public void cleanupForNextTest() {
705     getChangedFilesCollector().ensureUpToDate();
706
707     myTransactionMap = SmartFMap.emptyMap();
708     IndexConfiguration state = getState();
709     for (ID<?, ?> indexId : state.getIndexIDs()) {
710       final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
711       assert index != null;
712       index.cleanupForNextTest();
713     }
714   }
715
716   @ApiStatus.Internal
717   public ChangedFilesCollector getChangedFilesCollector() {
718     return myChangedFilesCollector.getValue();
719   }
720
721   void incrementFilesModCount() {
722     myFilesModCount.incrementAndGet();
723   }
724
725   void filesUpdateStarted(Project project) {
726     getChangedFilesCollector().ensureUpToDate();
727     myProjectsBeingUpdated.add(project);
728     incrementFilesModCount();
729   }
730
731   void filesUpdateFinished(@NotNull Project project) {
732     myProjectsBeingUpdated.remove(project);
733     incrementFilesModCount();
734   }
735
736   private final Lock myCalcIndexableFilesLock = new ReentrantLock();
737
738   @Override
739   @Nullable
740   public ProjectIndexableFilesFilter projectIndexableFiles(@Nullable Project project) {
741     if (project == null || project.isDefault() || getChangedFilesCollector().isUpdateInProgress()) return null;
742     if (myProjectsBeingUpdated.contains(project) || !UnindexedFilesUpdater.isProjectContentFullyScanned(project)) return null;
743
744     SoftReference<ProjectIndexableFilesFilter> reference = project.getUserData(ourProjectFilesSetKey);
745     ProjectIndexableFilesFilter data = com.intellij.reference.SoftReference.dereference(reference);
746     int currentFileModCount = myFilesModCount.get();
747     if (data != null && data.getModificationCount() == currentFileModCount) return data;
748
749     if (myCalcIndexableFilesLock.tryLock()) { // make best effort for calculating filter
750       try {
751         reference = project.getUserData(ourProjectFilesSetKey);
752         data = com.intellij.reference.SoftReference.dereference(reference);
753         if (data != null) {
754           if (data.getModificationCount() == currentFileModCount) {
755             return data;
756           }
757         } else if (!isUpToDateCheckEnabled()) {
758           return null;
759         }
760
761         long start = System.currentTimeMillis();
762
763         IntArrayList fileSet = new IntArrayList();
764         iterateIndexableFiles(fileOrDir -> {
765           if (fileOrDir instanceof VirtualFileWithId) {
766             fileSet.add(((VirtualFileWithId)fileOrDir).getId());
767           }
768           return true;
769         }, project, null);
770         ProjectIndexableFilesFilter filter = new ProjectIndexableFilesFilter(fileSet, currentFileModCount);
771         project.putUserData(ourProjectFilesSetKey, new SoftReference<>(filter));
772
773         long finish = System.currentTimeMillis();
774         LOG.debug(fileSet.size() + " files iterated in " + (finish - start) + " ms");
775
776         return filter;
777       }
778       finally {
779         myCalcIndexableFilesLock.unlock();
780       }
781     }
782     return null; // ok, no filtering
783   }
784
785   @Nullable
786   public static Throwable getCauseToRebuildIndex(@NotNull RuntimeException e) {
787     if (ApplicationManager.getApplication().isUnitTestMode()) {
788       // avoid rebuilding index in tests since we do it synchronously in requestRebuild and we can have readAction at hand
789       return null;
790     }
791     if (e instanceof ProcessCanceledException) {
792       return null;
793     }
794     if (e instanceof MapReduceIndex.MapInputException) {
795       // If exception has happened on input mapping (DataIndexer.map),
796       // it is handled as the indexer exception and must not lead to index rebuild.
797       return null;
798     }
799     if (e instanceof IndexOutOfBoundsException) return e; // something wrong with direct byte buffer
800     Throwable cause = e.getCause();
801     if (cause instanceof StorageException
802         || cause instanceof IOException
803         || cause instanceof IllegalArgumentException
804     ) {
805       return cause;
806     }
807     return null;
808   }
809
810   private static void scheduleIndexRebuild(String reason) {
811     LOG.info("scheduleIndexRebuild, reason: " + reason);
812     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
813       DumbService.getInstance(project).queueTask(new UnindexedFilesUpdater(project));
814     }
815   }
816
817   void clearIndicesIfNecessary() {
818     waitUntilIndicesAreInitialized();
819     for (ID<?, ?> indexId : getState().getIndexIDs()) {
820       try {
821         RebuildStatus.clearIndexIfNecessary(indexId, getIndex(indexId)::clear);
822       }
823       catch (StorageException e) {
824         requestRebuild(indexId);
825         LOG.error(e);
826       }
827     }
828   }
829
830   void clearIndex(@NotNull final ID<?, ?> indexId) throws StorageException {
831     advanceIndexVersion(indexId);
832
833     final UpdatableIndex<?, ?, FileContent> index = myRegisteredIndexes.getState().getIndex(indexId);
834     assert index != null : "Index with key " + indexId + " not found or not registered properly";
835     index.clear();
836   }
837
838   private void advanceIndexVersion(ID<?, ?> indexId) {
839     try {
840       IndexingStamp.rewriteVersion(indexId, myRegisteredIndexes.getState().getIndexVersion(indexId));
841     }
842     catch (IOException e) {
843       LOG.error(e);
844     }
845   }
846
847   @NotNull
848   private Set<Document> getUnsavedDocuments() {
849     Document[] documents = myFileDocumentManager.getUnsavedDocuments();
850     if (documents.length == 0) return Collections.emptySet();
851     if (documents.length == 1) return Collections.singleton(documents[0]);
852     return ContainerUtil.set(documents);
853   }
854
855   @NotNull
856   private Set<Document> getTransactedDocuments() {
857     return myTransactionMap.keySet();
858   }
859
860   private void indexUnsavedDocuments(@NotNull final ID<?, ?> indexId,
861                                      @Nullable Project project,
862                                      final GlobalSearchScope filter,
863                                      final VirtualFile restrictedFile) {
864     if (myUpToDateIndicesForUnsavedOrTransactedDocuments.contains(indexId)) {
865       return; // no need to index unsaved docs        // todo: check scope ?
866     }
867
868     Collection<Document> documents = getUnsavedDocuments();
869     Set<Document> transactedDocuments = getTransactedDocuments();
870     if (documents.isEmpty()) {
871       documents = transactedDocuments;
872     }
873     else if (!transactedDocuments.isEmpty()) {
874       documents = new HashSet<>(documents);
875       documents.addAll(transactedDocuments);
876     }
877     Document[] uncommittedDocuments = project != null ? PsiDocumentManager.getInstance(project).getUncommittedDocuments() : Document.EMPTY_ARRAY;
878     if (uncommittedDocuments.length > 0) {
879       List<Document> uncommittedDocumentsCollection = Arrays.asList(uncommittedDocuments);
880       if (documents.isEmpty()) documents = uncommittedDocumentsCollection;
881       else {
882         if (!(documents instanceof HashSet)) documents = new HashSet<>(documents);
883
884         documents.addAll(uncommittedDocumentsCollection);
885       }
886     }
887
888     if (!documents.isEmpty()) {
889       Collection<Document> documentsToProcessForProject = ContainerUtil.filter(documents,
890                                                                                document -> belongsToScope(myFileDocumentManager.getFile(document), restrictedFile, filter));
891
892       if (!documentsToProcessForProject.isEmpty()) {
893         UpdateTask<Document> task = myRegisteredIndexes.getUnsavedDataUpdateTask(indexId);
894         assert task != null : "Task for unsaved data indexing was not initialized for index " + indexId;
895
896         if(myStorageBufferingHandler.runUpdate(true, () -> task.processAll(documentsToProcessForProject, project)) &&
897            documentsToProcessForProject.size() == documents.size() &&
898            !hasActiveTransactions()
899           ) {
900           ProgressManager.checkCanceled();
901           myUpToDateIndicesForUnsavedOrTransactedDocuments.add(indexId);
902         }
903       }
904     }
905   }
906
907   private boolean hasActiveTransactions() {
908     return !myTransactionMap.isEmpty();
909   }
910
911
912   private static final Key<WeakReference<FileContentImpl>> ourFileContentKey = Key.create("unsaved.document.index.content");
913
914   // returns false if doc was not indexed because it is already up to date
915   // return true if document was indexed
916   // caller is responsible to ensure no concurrent same document processing
917   void indexUnsavedDocument(@NotNull final Document document, @NotNull final ID<?, ?> requestedIndexId, final Project project,
918                             @NotNull final VirtualFile vFile) {
919     final PsiFile dominantContentFile = project == null ? null : findLatestKnownPsiForUncomittedDocument(document, project);
920
921     final DocumentContent content;
922     if (dominantContentFile != null && dominantContentFile.getViewProvider().getModificationStamp() != document.getModificationStamp()) {
923       content = new PsiContent(document, dominantContentFile);
924     }
925     else {
926       content = new AuthenticContent(document);
927     }
928
929     final long currentDocStamp = PsiDocumentManager.getInstance(project).getLastCommittedStamp(document);
930
931     final long previousDocStamp = myLastIndexedDocStamps.get(document, requestedIndexId);
932     if (previousDocStamp == currentDocStamp) return;
933
934     final CharSequence contentText = content.getText();
935     getFileTypeManager().freezeFileTypeTemporarilyIn(vFile, () -> {
936       if (getAffectedIndexCandidates(vFile).contains(requestedIndexId) &&
937           getInputFilter(requestedIndexId).acceptInput(vFile)) {
938         final int inputId = Math.abs(getFileId(vFile));
939
940         if (!isTooLarge(vFile, (long)contentText.length())) {
941           // Reasonably attempt to use same file content when calculating indices as we can evaluate them several at once and store in file content
942           WeakReference<FileContentImpl> previousContentRef = document.getUserData(ourFileContentKey);
943           FileContentImpl previousContent = com.intellij.reference.SoftReference.dereference(previousContentRef);
944           final FileContentImpl newFc;
945           if (previousContent != null && previousContent.getStamp() == currentDocStamp) {
946             newFc = previousContent;
947           }
948           else {
949             newFc = new FileContentImpl(vFile, contentText, currentDocStamp);
950             document.putUserData(ourFileContentKey, new WeakReference<>(newFc));
951           }
952
953           initFileContent(newFc, project, dominantContentFile);
954           newFc.ensureThreadSafeLighterAST();
955
956           if (content instanceof AuthenticContent) {
957             newFc.putUserData(PlatformIdTableBuilding.EDITOR_HIGHLIGHTER,
958                               EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document));
959           }
960
961           markFileIndexed(vFile);
962           try {
963             getIndex(requestedIndexId).mapInputAndPrepareUpdate(inputId, newFc).compute();
964           }
965           finally {
966             unmarkBeingIndexed();
967             cleanFileContent(newFc, dominantContentFile);
968           }
969         }
970         else { // effectively wipe the data from the indices
971           getIndex(requestedIndexId).mapInputAndPrepareUpdate(inputId, null).compute();
972         }
973       }
974
975       long previousState = myLastIndexedDocStamps.set(document, requestedIndexId, currentDocStamp);
976       assert previousState == previousDocStamp;
977     });
978   }
979
980   @NotNull
981   @Override
982   public <K, V> Map<K, V> getFileData(@NotNull ID<K, V> id, @NotNull VirtualFile virtualFile, @NotNull Project project) {
983     if (ModelBranch.getFileBranch(virtualFile) != null) {
984       return getInMemoryData(id, virtualFile, project);
985     }
986
987     return super.getFileData(id, virtualFile, project);
988   }
989
990   @SuppressWarnings({"unchecked", "rawtypes"})
991   @NotNull
992   private <K, V> Map<K, V> getInMemoryData(@NotNull ID<K, V> id, @NotNull VirtualFile virtualFile, @NotNull Project project) {
993     PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
994     if (psiFile != null) {
995       Map<ID, Map> indexValues = CachedValuesManager.getCachedValue(psiFile, () -> {
996         try {
997           FileContentImpl fc = psiFile instanceof PsiBinaryFile ? FileContentImpl.createByFile(virtualFile, null)
998                                                                 : new FileContentImpl(virtualFile, psiFile.getViewProvider().getContents(), 0);
999           initFileContent(fc, project, psiFile);
1000           Map<ID, Map> result = FactoryMap.create(key -> getIndex(key).getExtension().getIndexer().map(fc));
1001           return CachedValueProvider.Result.createSingleDependency(result, psiFile);
1002         }
1003         catch (IOException e) {
1004           throw new RuntimeException(e);
1005         }
1006       });
1007       return indexValues.get(id);
1008     }
1009     return Collections.emptyMap();
1010   }
1011
1012
1013   private final StorageBufferingHandler myStorageBufferingHandler = new StorageBufferingHandler() {
1014     @NotNull
1015     @Override
1016     protected Stream<UpdatableIndex<?, ?, ?>> getIndexes() {
1017       IndexConfiguration state = getState();
1018       return state.getIndexIDs().stream().map(id -> state.getIndex(id));
1019     }
1020   };
1021
1022   @ApiStatus.Internal
1023   public void runCleanupAction(@NotNull Runnable cleanupAction) {
1024     Computable<Boolean> updateComputable = () -> {
1025       cleanupAction.run();
1026       return true;
1027     };
1028     myStorageBufferingHandler.runUpdate(false, updateComputable);
1029     myStorageBufferingHandler.runUpdate(true, updateComputable);
1030   }
1031
1032   void cleanupMemoryStorage(boolean skipContentDependentIndexes) {
1033     myLastIndexedDocStamps.clear();
1034     if (myRegisteredIndexes == null) {
1035       // unsaved doc is dropped while plugin load/unload-ing
1036       return;
1037     }
1038     IndexConfiguration state = myRegisteredIndexes.getState();
1039     if (state == null) {
1040       // avoid waiting for end of indices initialization (IDEA-173382)
1041       // in memory content will appear on indexing (in read action) and here is event dispatch (write context)
1042       return;
1043     }
1044     for (ID<?, ?> indexId : state.getIndexIDs()) {
1045       if (skipContentDependentIndexes && myRegisteredIndexes.isContentDependentIndex(indexId)) continue;
1046       final UpdatableIndex<?, ?, FileContent> index = state.getIndex(indexId);
1047       assert index != null;
1048       index.cleanupMemoryStorage();
1049     }
1050   }
1051
1052   @Override
1053   public void requestRebuild(@NotNull final ID<?, ?> indexId, final @NotNull Throwable throwable) {
1054     if (!myRegisteredIndexes.isExtensionsDataLoaded()) {
1055       IndexInfrastructure.submitGenesisTask(() -> {
1056         waitUntilIndicesAreInitialized(); // should be always true here since the genesis pool is sequential
1057         doRequestRebuild(indexId, throwable);
1058         return null;
1059       });
1060     }
1061     else {
1062       doRequestRebuild(indexId, throwable);
1063     }
1064   }
1065
1066   private void doRequestRebuild(@NotNull ID<?, ?> indexId, Throwable throwable) {
1067     cleanupProcessedFlag();
1068     if (!myRegisteredIndexes.isExtensionsDataLoaded()) reportUnexpectedAsyncInitState();
1069
1070     if (RebuildStatus.requestRebuild(indexId)) {
1071       String message = "Rebuild requested for index " + indexId;
1072       Application app = ApplicationManager.getApplication();
1073       if (app.isUnitTestMode() && app.isReadAccessAllowed() && !app.isDispatchThread()) {
1074         // shouldn't happen in tests in general; so fail early with the exception that caused index to be rebuilt.
1075         // otherwise reindexing will fail anyway later, but with a much more cryptic assertion
1076         LOG.error(message, throwable);
1077       }
1078       else {
1079         LOG.info(message, throwable);
1080       }
1081
1082       cleanupProcessedFlag();
1083
1084       if (!myRegisteredIndexes.isInitialized()) return;
1085       advanceIndexVersion(indexId);
1086
1087       Runnable rebuildRunnable = () -> scheduleIndexRebuild("checkRebuild");
1088
1089       if (myIsUnitTestMode) {
1090         rebuildRunnable.run();
1091       }
1092       else {
1093         // we do invoke later since we can have read lock acquired
1094         AppUIExecutor.onWriteThread().later().expireWith(app).submit(rebuildRunnable);
1095       }
1096     }
1097   }
1098
1099   private static void reportUnexpectedAsyncInitState() {
1100     LOG.error("Unexpected async indices initialization problem");
1101   }
1102
1103   @Override
1104   public <K, V> UpdatableIndex<K, V, FileContent> getIndex(ID<K, V> indexId) {
1105     return getState().getIndex(indexId);
1106   }
1107
1108   private InputFilter getInputFilter(@NotNull ID<?, ?> indexId) {
1109     if (!myRegisteredIndexes.isInitialized()) {
1110       // 1. early vfs event that needs invalidation
1111       // 2. pushers that do synchronous indexing for contentless indices
1112       waitUntilIndicesAreInitialized();
1113     }
1114
1115     return getState().getInputFilter(indexId);
1116   }
1117
1118   @NotNull
1119   Collection<VirtualFile> getFilesToUpdate(final Project project) {
1120     return ContainerUtil.filter(getChangedFilesCollector().getAllFilesToUpdate(), filesToBeIndexedForProjectCondition(project)::test);
1121   }
1122
1123   @NotNull
1124   private Predicate<VirtualFile> filesToBeIndexedForProjectCondition(Project project) {
1125     return virtualFile -> {
1126         if (!virtualFile.isValid()) {
1127           return true;
1128         }
1129
1130         for (IndexableFileSet set : myIndexableSets) {
1131           final Project proj = myIndexableSetToProjectMap.get(set);
1132           if (proj != null && !proj.equals(project)) {
1133             continue; // skip this set as associated with a different project
1134           }
1135           if (ReadAction.compute(() -> set.isInSet(virtualFile))) {
1136             return true;
1137           }
1138         }
1139         return false;
1140       };
1141   }
1142
1143   public boolean isFileUpToDate(VirtualFile file) {
1144     return !getChangedFilesCollector().isScheduledForUpdate(file);
1145   }
1146
1147   // caller is responsible to ensure no concurrent same document processing
1148   private void processRefreshedFile(@Nullable Project project, @NotNull final CachedFileContent fileContent) {
1149     // ProcessCanceledException will cause re-adding the file to processing list
1150     final VirtualFile file = fileContent.getVirtualFile();
1151     if (getChangedFilesCollector().isScheduledForUpdate(file)) {
1152       indexFileContent(project, fileContent);
1153     }
1154   }
1155
1156   @ApiStatus.Internal
1157   @NotNull
1158   public FileIndexingStatistics indexFileContent(@Nullable Project project, @NotNull CachedFileContent content) {
1159     ProgressManager.checkCanceled();
1160     VirtualFile file = content.getVirtualFile();
1161     final int fileId = Math.abs(getIdMaskingNonIdBasedFile(file));
1162
1163     FileIndexingResult indexingResult;
1164     try {
1165       // if file was scheduled for update due to vfs events then it is present in myFilesToUpdate
1166       // in this case we consider that current indexing (out of roots backed CacheUpdater) will cover its content
1167       if (file.isValid() && content.getTimeStamp() != file.getTimeStamp()) {
1168         content = new CachedFileContent(file);
1169       }
1170       if (!file.isValid() || isTooLarge(file)) {
1171         ProgressManager.checkCanceled();
1172         removeDataFromIndicesForFile(fileId, file);
1173         if (file instanceof DeletedVirtualFileStub && ((DeletedVirtualFileStub)file).isResurrected()) {
1174           CachedFileContent resurrectedFileContent = new CachedFileContent(((DeletedVirtualFileStub)file).getOriginalFile());
1175           indexingResult = doIndexFileContent(project, resurrectedFileContent);
1176         } else {
1177           indexingResult = new FileIndexingResult(true, Collections.emptyMap(), file.getFileType());
1178         }
1179       }
1180       else {
1181         indexingResult = doIndexFileContent(project, content);
1182       }
1183
1184       if (indexingResult.setIndexedStatus && file instanceof VirtualFileSystemEntry) {
1185         ((VirtualFileSystemEntry)file).setFileIndexed(true);
1186       }
1187       if (VfsEventsMerger.LOG != null) {
1188         VfsEventsMerger.LOG.info("File " + file + " has been indexed for indexes " + indexingResult.timesPerIndexer.keySet());
1189       }
1190       getChangedFilesCollector().removeFileIdFromFilesScheduledForUpdate(fileId);
1191       // Indexing time takes only input data mapping time into account.
1192       long indexingTime = indexingResult.timesPerIndexer.values().stream().mapToLong(e -> e).sum();
1193       return new FileIndexingStatistics(indexingTime,
1194                                         indexingResult.fileType,
1195                                         indexingResult.timesPerIndexer);
1196     }
1197     finally {
1198       IndexingStamp.flushCache(fileId);
1199     }
1200   }
1201
1202   private static final class FileIndexingResult {
1203     public final boolean setIndexedStatus;
1204     public final Map<ID<?, ?>, Long> timesPerIndexer;
1205     public final FileType fileType;
1206
1207     private FileIndexingResult(boolean setIndexedStatus,
1208                                @NotNull Map<ID<?, ?>, Long> timesPerIndexer,
1209                                @NotNull FileType type) {
1210       this.setIndexedStatus = setIndexedStatus;
1211       this.timesPerIndexer = timesPerIndexer;
1212       fileType = type;
1213     }
1214   }
1215
1216   private static final class SingleIndexUpdateStats {
1217     public final long mapInputTime;
1218
1219     private SingleIndexUpdateStats(long mapInputTime) {
1220       this.mapInputTime = mapInputTime;
1221     }
1222   }
1223
1224   @NotNull
1225   private FileBasedIndexImpl.FileIndexingResult doIndexFileContent(@Nullable Project project, @NotNull CachedFileContent content) {
1226     ProgressManager.checkCanceled();
1227     final VirtualFile file = content.getVirtualFile();
1228     Ref<Boolean> setIndexedStatus = Ref.create(Boolean.TRUE);
1229     Map<ID<?, ?>, Long> perIndexerTimes = new HashMap<>();
1230     Ref<FileType> fileTypeRef = Ref.create();
1231
1232     getFileTypeManager().freezeFileTypeTemporarilyIn(file, () -> {
1233       ProgressManager.checkCanceled();
1234
1235       FileContentImpl fc = null;
1236       PsiFile psiFile = null;
1237
1238       int inputId = Math.abs(getFileId(file));
1239       Set<ID<?, ?>> currentIndexedStates = new HashSet<>(IndexingStamp.getNontrivialFileIndexedStates(inputId));
1240       List<ID<?, ?>> affectedIndexCandidates = getAffectedIndexCandidates(file);
1241       //noinspection ForLoopReplaceableByForEach
1242       for (int i = 0, size = affectedIndexCandidates.size(); i < size; ++i) {
1243         try {
1244           ProgressManager.checkCanceled();
1245
1246           if (fc == null) {
1247             fc = new LazyFileContentImpl(file, () -> getBytesOrNull(content));
1248
1249             ProgressManager.checkCanceled();
1250
1251             psiFile = content.getUserData(IndexingDataKeys.PSI_FILE);
1252             initFileContent(fc, project == null ? ProjectUtil.guessProjectForFile(file) : project, psiFile);
1253
1254             fileTypeRef.set(fc.getFileType());
1255
1256             ProgressManager.checkCanceled();
1257           }
1258
1259           final ID<?, ?> indexId = affectedIndexCandidates.get(i);
1260           if (getInputFilter(indexId).acceptInput(file) && getIndexingState(fc, indexId).updateRequired()) {
1261             ProgressManager.checkCanceled();
1262             SingleIndexUpdateStats updateStats = updateSingleIndex(indexId, file, inputId, fc);
1263             if (updateStats == null) {
1264               setIndexedStatus.set(Boolean.FALSE);
1265             } else {
1266               perIndexerTimes.put(indexId, updateStats.mapInputTime);
1267             }
1268             currentIndexedStates.remove(indexId);
1269           }
1270         }
1271         catch (ProcessCanceledException e) {
1272           cleanFileContent(fc, psiFile);
1273           throw e;
1274         }
1275       }
1276
1277       if (psiFile != null) {
1278         psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1279       }
1280
1281       boolean shouldClearAllIndexedStates = fc == null;
1282       for (ID<?, ?> indexId : currentIndexedStates) {
1283         ProgressManager.checkCanceled();
1284         if (shouldClearAllIndexedStates || getIndex(indexId).getIndexingStateForFile(inputId, fc).updateRequired()) {
1285           ProgressManager.checkCanceled();
1286           SingleIndexUpdateStats updateStats = updateSingleIndex(indexId, file, inputId, null);
1287           if (updateStats == null) {
1288             setIndexedStatus.set(Boolean.FALSE);
1289           } else {
1290             perIndexerTimes.put(indexId, updateStats.mapInputTime);
1291           }
1292         }
1293       }
1294
1295       fileTypeRef.set(fc != null ? fc.getFileType() : file.getFileType());
1296     });
1297
1298     file.putUserData(IndexingDataKeys.REBUILD_REQUESTED, null);
1299     return new FileIndexingResult(setIndexedStatus.get(), perIndexerTimes, fileTypeRef.get());
1300   }
1301
1302   private static byte @NotNull[] getBytesOrNull(@NotNull CachedFileContent content) {
1303     try {
1304       return content.getBytes();
1305     } catch (IOException e) {
1306       return ArrayUtilRt.EMPTY_BYTE_ARRAY;
1307     }
1308   }
1309
1310   @Override
1311   public boolean isIndexingCandidate(@NotNull VirtualFile file, @NotNull ID<?, ?> indexId) {
1312     return !isTooLarge(file) && getAffectedIndexCandidates(file).contains(indexId);
1313   }
1314
1315   @NotNull
1316   List<ID<?, ?>> getAffectedIndexCandidates(@NotNull VirtualFile file) {
1317     if (file.isDirectory()) {
1318       return isProjectOrWorkspaceFile(file, null) ? Collections.emptyList() : myRegisteredIndexes.getIndicesForDirectories();
1319     }
1320     FileType fileType = file.getFileType();
1321     if(isProjectOrWorkspaceFile(file, fileType)) return Collections.emptyList();
1322
1323     return getState().getFileTypesForIndex(fileType);
1324   }
1325
1326   private static void cleanFileContent(FileContentImpl fc, PsiFile psiFile) {
1327     if (fc == null) return;
1328     if (psiFile != null) psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1329     fc.putUserData(IndexingDataKeys.PSI_FILE, null);
1330   }
1331
1332   private static void initFileContent(@NotNull FileContentImpl fc, Project project, PsiFile psiFile) {
1333     if (psiFile != null) {
1334       psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1335       fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
1336     }
1337
1338     fc.setProject(project);
1339   }
1340
1341   @Nullable("null in case index update is not necessary or the update has failed")
1342   SingleIndexUpdateStats updateSingleIndex(@NotNull ID<?, ?> indexId, @Nullable VirtualFile file, int inputId, @Nullable FileContent currentFC) {
1343     if (!myRegisteredIndexes.isExtensionsDataLoaded()) reportUnexpectedAsyncInitState();
1344     if (!RebuildStatus.isOk(indexId) && !myIsUnitTestMode) {
1345       return null; // the index is scheduled for rebuild, no need to update
1346     }
1347     myLocalModCount.incrementAndGet();
1348
1349     final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1350     assert index != null;
1351
1352     markFileIndexed(file);
1353     try {
1354       Computable<Boolean> storageUpdate;
1355       long mapInputTime = System.nanoTime();
1356       try {
1357         // Propagate MapReduceIndex.MapInputException and ProcessCancelledException happening on input mapping.
1358         storageUpdate = index.mapInputAndPrepareUpdate(inputId, currentFC);
1359       } finally {
1360         mapInputTime = System.nanoTime() - mapInputTime;
1361       }
1362       if (myStorageBufferingHandler.runUpdate(false, storageUpdate)) {
1363         ConcurrencyUtil.withLock(myReadLock, () -> {
1364           if (currentFC != null) {
1365             if (!isMock(currentFC.getFile())) {
1366               index.setIndexedStateForFile(inputId, currentFC);
1367             }
1368           }
1369           else {
1370             index.resetIndexedStateForFile(inputId);
1371           }
1372         });
1373       }
1374       return new SingleIndexUpdateStats(mapInputTime);
1375     }
1376     catch (RuntimeException exception) {
1377       Throwable causeToRebuildIndex = getCauseToRebuildIndex(exception);
1378       if (causeToRebuildIndex != null) {
1379         requestRebuild(indexId, exception);
1380         return null;
1381       }
1382       throw exception;
1383     }
1384     finally {
1385       unmarkBeingIndexed();
1386     }
1387   }
1388
1389   private static void markFileIndexed(@Nullable VirtualFile file) {
1390     if (ourIndexedFile.get() != null || ourFileToBeIndexed.get() != null) {
1391       throw new AssertionError("Reentrant indexing");
1392     }
1393     ourIndexedFile.set(file);
1394   }
1395
1396   private static void unmarkBeingIndexed() {
1397     ourIndexedFile.remove();
1398   }
1399
1400   @Override
1401   public VirtualFile getFileBeingCurrentlyIndexed() {
1402     return ourIndexedFile.get();
1403   }
1404
1405   private class VirtualFileUpdateTask extends UpdateTask<VirtualFile> {
1406     @Override
1407     void doProcess(VirtualFile item, Project project) {
1408       processRefreshedFile(project, new CachedFileContent(item));
1409     }
1410   }
1411
1412   private final VirtualFileUpdateTask myForceUpdateTask = new VirtualFileUpdateTask();
1413   private volatile long myLastOtherProjectInclusionStamp;
1414
1415   private void forceUpdate(@Nullable Project project, @Nullable final GlobalSearchScope filter, @Nullable final VirtualFile restrictedTo) {
1416     Collection<VirtualFile> allFilesToUpdate = getChangedFilesCollector().getAllFilesToUpdate();
1417
1418     if (!allFilesToUpdate.isEmpty()) {
1419       boolean includeFilesFromOtherProjects = restrictedTo == null && System.currentTimeMillis() - myLastOtherProjectInclusionStamp > 100;
1420       List<VirtualFile> virtualFilesToBeUpdatedForProject = ContainerUtil.filter(
1421         allFilesToUpdate,
1422         new ProjectFilesCondition(projectIndexableFiles(project), filter, restrictedTo,
1423                                   includeFilesFromOtherProjects)
1424       );
1425
1426       if (!virtualFilesToBeUpdatedForProject.isEmpty()) {
1427         myForceUpdateTask.processAll(virtualFilesToBeUpdatedForProject, project);
1428       }
1429       if (includeFilesFromOtherProjects) {
1430         myLastOtherProjectInclusionStamp = System.currentTimeMillis();
1431       }
1432     }
1433   }
1434
1435   public boolean needsFileContentLoading(@NotNull ID<?, ?> indexId) {
1436     return myRegisteredIndexes.isContentDependentIndex(indexId);
1437   }
1438
1439   @Nullable IndexableFileSet getIndexableSetForFile(VirtualFile file) {
1440     for (IndexableFileSet set : myIndexableSets) {
1441       if (set.isInSet(file)) {
1442         return set;
1443       }
1444     }
1445     return null;
1446   }
1447
1448   @NotNull List<IndexableFileSet> getIndexableSets() {
1449     return myIndexableSets;
1450   }
1451
1452   @ApiStatus.Internal
1453   public void dropNontrivialIndexedStates(int inputId) {
1454     for (ID<?, ?> state : IndexingStamp.getNontrivialFileIndexedStates(inputId)) {
1455       getIndex(state).resetIndexedStateForFile(inputId);
1456     }
1457   }
1458
1459   void doTransientStateChangeForFile(int fileId, @NotNull VirtualFile file) {
1460     waitUntilIndicesAreInitialized();
1461     if (!clearUpToDateStateForPsiIndicesOfUnsavedDocuments(file, IndexingStamp.getNontrivialFileIndexedStates(fileId))) {
1462       // change in persistent file
1463       clearUpToDateStateForPsiIndicesOfVirtualFile(file);
1464     }
1465   }
1466
1467   void doInvalidateIndicesForFile(int fileId, @NotNull VirtualFile file, boolean contentChanged) {
1468     waitUntilIndicesAreInitialized();
1469     cleanProcessedFlag(file);
1470
1471     List<ID<?, ?>> nontrivialFileIndexedStates = IndexingStamp.getNontrivialFileIndexedStates(fileId);
1472     Collection<ID<?, ?>> fileIndexedStatesToUpdate = ContainerUtil.intersection(nontrivialFileIndexedStates, myRegisteredIndexes.getRequiringContentIndices());
1473
1474     // transient index value can depend on disk value because former is diff to latter
1475     // it doesn't matter content hanged or not: indices might depend on file name too
1476     removeTransientFileDataFromIndices(nontrivialFileIndexedStates, fileId, file);
1477
1478     if (contentChanged) {
1479       // only mark the file as outdated, reindex will be done lazily
1480       if (!fileIndexedStatesToUpdate.isEmpty()) {
1481         //noinspection ForLoopReplaceableByForEach
1482         for (int i = 0, size = nontrivialFileIndexedStates.size(); i < size; ++i) {
1483           final ID<?, ?> indexId = nontrivialFileIndexedStates.get(i);
1484           if (needsFileContentLoading(indexId)) {
1485             getIndex(indexId).resetIndexedStateForFile(fileId);
1486           }
1487         }
1488
1489         // the file is for sure not a dir and it was previously indexed by at least one index
1490         if (file.isValid()) {
1491           if (!isTooLarge(file)) {
1492             getChangedFilesCollector().scheduleForUpdate(file);
1493           }
1494           else getChangedFilesCollector().scheduleForUpdate(new DeletedVirtualFileStub((VirtualFileWithId)file));
1495         }
1496         else {
1497           LOG.info("Unexpected state in update:" + file);
1498         }
1499       }
1500     }
1501     else { // file was removed
1502       for (ID<?, ?> indexId : nontrivialFileIndexedStates) {
1503         if (!myRegisteredIndexes.isContentDependentIndex(indexId)) {
1504           updateSingleIndex(indexId, null, fileId, null);
1505         }
1506       }
1507       if (!fileIndexedStatesToUpdate.isEmpty()) {
1508         // its data should be (lazily) wiped for every index
1509         getChangedFilesCollector().scheduleForUpdate(new DeletedVirtualFileStub((VirtualFileWithId)file));
1510       }
1511       else {
1512         getChangedFilesCollector().removeScheduledFileFromUpdate(file); // no need to update it anymore
1513       }
1514     }
1515   }
1516
1517   void scheduleFileForIndexing(int fileId, @NotNull VirtualFile file, boolean contentChange) {
1518     final List<IndexableFilesFilter> filters = IndexableFilesFilter.EP_NAME.getExtensionList();
1519     if (!filters.isEmpty() && !ContainerUtil.exists(filters, e -> e.shouldIndex(file))) return;
1520
1521     // handle 'content-less' indices separately
1522     boolean fileIsDirectory = file.isDirectory();
1523
1524     if (!contentChange) {
1525       FileContent fileContent = null;
1526       for (ID<?, ?> indexId : getContentLessIndexes(fileIsDirectory)) {
1527         if (getInputFilter(indexId).acceptInput(file)) {
1528           if (fileContent == null) {
1529             fileContent = new IndexedFileWrapper(new IndexedFileImpl(file, null));
1530           }
1531           updateSingleIndex(indexId, file, fileId, fileContent);
1532         }
1533       }
1534     }
1535     // For 'normal indices' schedule the file for update and reset stamps for all affected indices (there
1536     // can be client that used indices between before and after events, in such case indices are up to date due to force update
1537     // with old content)
1538     if (!fileIsDirectory) {
1539       if (!file.isValid() || isTooLarge(file)) {
1540         // large file might be scheduled for update in before event when its size was not large
1541         getChangedFilesCollector().removeScheduledFileFromUpdate(file);
1542       }
1543       else {
1544         ourFileToBeIndexed.set(file);
1545         try {
1546           getFileTypeManager().freezeFileTypeTemporarilyIn(file, () -> {
1547             final List<ID<?, ?>> candidates = getAffectedIndexCandidates(file);
1548
1549             boolean scheduleForUpdate = false;
1550
1551             //noinspection ForLoopReplaceableByForEach
1552             for (int i = 0, size = candidates.size(); i < size; ++i) {
1553               final ID<?, ?> indexId = candidates.get(i);
1554               if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) {
1555                 getIndex(indexId).resetIndexedStateForFile(fileId);
1556                 scheduleForUpdate = true;
1557               }
1558             }
1559
1560             if (scheduleForUpdate) {
1561               IndexingStamp.flushCache(fileId);
1562               getChangedFilesCollector().scheduleForUpdate(file);
1563             }
1564             else if (file instanceof VirtualFileSystemEntry) {
1565               ((VirtualFileSystemEntry)file).setFileIndexed(true);
1566             }
1567           });
1568         } finally {
1569           ourFileToBeIndexed.remove();
1570         }
1571       }
1572     }
1573   }
1574
1575   @NotNull
1576   Collection<ID<?, ?>> getContentLessIndexes(boolean isDirectory) {
1577     return isDirectory ? myRegisteredIndexes.getIndicesForDirectories() : myRegisteredIndexes.getNotRequiringContentIndices();
1578   }
1579
1580   @NotNull
1581   public Collection<ID<?, ?>> getContentDependentIndexes() {
1582     return myRegisteredIndexes.getRequiringContentIndices();
1583   }
1584
1585   static FileTypeManagerImpl getFileTypeManager() {
1586     return (FileTypeManagerImpl)FileTypeManager.getInstance();
1587   }
1588
1589   private boolean clearUpToDateStateForPsiIndicesOfUnsavedDocuments(@NotNull VirtualFile file, Collection<? extends ID<?, ?>> affectedIndices) {
1590     clearUpToDateIndexesForUnsavedOrTransactedDocs();
1591
1592     Document document = myFileDocumentManager.getCachedDocument(file);
1593
1594     if (document != null && myFileDocumentManager.isDocumentUnsaved(document)) {   // will be reindexed in indexUnsavedDocuments
1595       myLastIndexedDocStamps.clearForDocument(document); // Q: non psi indices
1596       document.putUserData(ourFileContentKey, null);
1597
1598       return true;
1599     }
1600
1601     removeTransientFileDataFromIndices(ContainerUtil.intersection(affectedIndices, myRegisteredIndexes.getRequiringContentIndices()), getFileId(file), file);
1602     return false;
1603   }
1604
1605   void clearUpToDateIndexesForUnsavedOrTransactedDocs() {
1606     if (!myUpToDateIndicesForUnsavedOrTransactedDocuments.isEmpty()) {
1607       myUpToDateIndicesForUnsavedOrTransactedDocuments.clear();
1608     }
1609   }
1610
1611   static int getIdMaskingNonIdBasedFile(@NotNull VirtualFile file) {
1612     return file instanceof VirtualFileWithId ?((VirtualFileWithId)file).getId() : IndexingStamp.INVALID_FILE_ID;
1613   }
1614
1615   FileIndexingState shouldIndexFile(@NotNull IndexedFile file, @NotNull ID<?, ?> indexId) {
1616     if (!getInputFilter(indexId).acceptInput(file.getFile())) {
1617       return getIndexingState(file, indexId) == FileIndexingState.NOT_INDEXED
1618              ? FileIndexingState.UP_TO_DATE
1619              : FileIndexingState.OUT_DATED;
1620     }
1621     return getIndexingState(file, indexId);
1622   }
1623
1624   @NotNull
1625   private FileIndexingState getIndexingState(@NotNull IndexedFile file, @NotNull ID<?, ?> indexId) {
1626     VirtualFile virtualFile = file.getFile();
1627     if (isMock(virtualFile)) return FileIndexingState.NOT_INDEXED;
1628     return getIndex(indexId).getIndexingStateForFile(((NewVirtualFile)virtualFile).getId(), file);
1629   }
1630
1631   static boolean isMock(final VirtualFile file) {
1632     return !(file instanceof NewVirtualFile);
1633   }
1634
1635   public boolean isTooLarge(@NotNull VirtualFile file) {
1636     return isTooLarge(file, null);
1637   }
1638
1639   public boolean isTooLarge(@NotNull VirtualFile file,
1640                             @Nullable("if content size should be retrieved from a file") Long contentSize) {
1641     return isTooLarge(file, contentSize, myRegisteredIndexes.getNoLimitCheckFileTypes());
1642   }
1643
1644   @ApiStatus.Internal
1645   public static boolean isTooLarge(@NotNull VirtualFile file,
1646                                    @Nullable("if content size should be retrieved from a file") Long contentSize,
1647                                    @NotNull Set<FileType> noLimitFileTypes) {
1648     if (SingleRootFileViewProvider.isTooLargeForIntelligence(file, contentSize)) {
1649       return !noLimitFileTypes.contains(file.getFileType()) || SingleRootFileViewProvider.isTooLargeForContentLoading(file, contentSize);
1650     }
1651     return false;
1652   }
1653
1654   @Override
1655   public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) {
1656     myIndexableSets.add(set);
1657     myIndexableSetToProjectMap.put(set, project);
1658     if (project != null) {
1659       ((PsiManagerImpl)PsiManager.getInstance(project)).addTreeChangePreprocessor(event -> {
1660         if (event.isGenericChange() &&
1661             event.getCode() == PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED) {
1662           PsiFile file = event.getFile();
1663
1664           if (file != null) {
1665             VirtualFile virtualFile = file.getVirtualFile();
1666             if (virtualFile instanceof VirtualFileWithId && !isMock(virtualFile)) {
1667               getChangedFilesCollector().getEventMerger().recordTransientStateChangeEvent(virtualFile);
1668             }
1669           }
1670         }
1671       });
1672     }
1673   }
1674
1675   private void clearUpToDateStateForPsiIndicesOfVirtualFile(VirtualFile virtualFile) {
1676     if (virtualFile instanceof VirtualFileWithId) {
1677       int fileId = ((VirtualFileWithId)virtualFile).getId();
1678       boolean wasIndexed = false;
1679       List<ID<?, ?>> candidates = getAffectedIndexCandidates(virtualFile);
1680       for (ID<?, ?> candidate : candidates) {
1681         if (myRegisteredIndexes.isContentDependentIndex(candidate)) {
1682           if(getInputFilter(candidate).acceptInput(virtualFile)) {
1683             getIndex(candidate).resetIndexedStateForFile(fileId);
1684             wasIndexed = true;
1685           }
1686         }
1687       }
1688       if (wasIndexed) {
1689         getChangedFilesCollector().scheduleForUpdate(virtualFile);
1690         IndexingStamp.flushCache(fileId);
1691       }
1692     }
1693   }
1694
1695   @Override
1696   public void removeIndexableSet(@NotNull IndexableFileSet set) {
1697     if (!myIndexableSetToProjectMap.containsKey(set)) return;
1698     myIndexableSets.remove(set);
1699     myIndexableSetToProjectMap.remove(set);
1700
1701     ChangedFilesCollector changedFilesCollector = getChangedFilesCollector();
1702     for (VirtualFile file : changedFilesCollector.getAllFilesToUpdate()) {
1703       final int fileId = Math.abs(getIdMaskingNonIdBasedFile(file));
1704       if (!file.isValid()) {
1705         removeDataFromIndicesForFile(fileId, file);
1706         changedFilesCollector.removeFileIdFromFilesScheduledForUpdate(fileId);
1707       }
1708       else if (getIndexableSetForFile(file) == null) { // todo remove data from indices for removed
1709         changedFilesCollector.removeFileIdFromFilesScheduledForUpdate(fileId);
1710       }
1711     }
1712
1713     IndexingStamp.flushCaches();
1714   }
1715
1716   @Override
1717   public VirtualFile findFileById(Project project, int id) {
1718     return IndexInfrastructure.findFileById((PersistentFS)ManagingFS.getInstance(), id);
1719   }
1720
1721   @Nullable
1722   private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) {
1723     return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc);
1724   }
1725
1726   @VisibleForTesting
1727   public static void cleanupProcessedFlag() {
1728     final VirtualFile[] roots = ManagingFS.getInstance().getRoots();
1729     for (VirtualFile root : roots) {
1730       cleanProcessedFlag(root);
1731     }
1732   }
1733
1734   static void cleanProcessedFlag(@NotNull final VirtualFile file) {
1735     if (!(file instanceof VirtualFileSystemEntry)) return;
1736
1737     final VirtualFileSystemEntry nvf = (VirtualFileSystemEntry)file;
1738     nvf.setFileIndexed(false);
1739     if (file.isDirectory()) {
1740       for (VirtualFile child : nvf.getCachedChildren()) {
1741         cleanProcessedFlag(child);
1742       }
1743     }
1744   }
1745
1746   void setUpFlusher() {
1747     myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() {
1748       private final SerializationManagerEx mySerializationManager = SerializationManagerEx.getInstanceEx();
1749       private int lastModCount;
1750
1751       @Override
1752       public void run() {
1753         mySerializationManager.flushNameStorage();
1754
1755         int currentModCount = myLocalModCount.get();
1756         if (lastModCount == currentModCount) {
1757           flushAllIndices(lastModCount);
1758         }
1759         lastModCount = currentModCount;
1760       }
1761     });
1762   }
1763
1764   @Override
1765   public void invalidateCaches() {
1766     CorruptionMarker.requestInvalidation();
1767   }
1768
1769   @Override
1770   @ApiStatus.Internal
1771   @NotNull
1772   public IntPredicate getAccessibleFileIdFilter(@Nullable Project project) {
1773     boolean dumb = ActionUtil.isDumbMode(project);
1774     if (!dumb) return f -> true;
1775
1776     DumbModeAccessType dumbModeAccessType = getCurrentDumbModeAccessType();
1777     if (dumbModeAccessType == null) {
1778       //throw new IllegalStateException("index access is not allowed in dumb mode");
1779       return __ -> true;
1780     }
1781
1782     if (dumbModeAccessType == DumbModeAccessType.RAW_INDEX_DATA_ACCEPTABLE) return f -> true;
1783
1784     assert dumbModeAccessType == DumbModeAccessType.RELIABLE_DATA_ONLY;
1785     return fileId -> !getChangedFilesCollector().containsFileId(fileId);
1786   }
1787
1788   @ApiStatus.Internal
1789   public void flushIndexes() {
1790     for (ID<?, ?> id : getRegisteredIndexes().getState().getIndexIDs()) {
1791       try {
1792         getIndex(id).flush();
1793       }
1794       catch (StorageException e) {
1795         throw new RuntimeException(e);
1796       }
1797     }
1798   }
1799
1800   public static final boolean DO_TRACE_STUB_INDEX_UPDATE = SystemProperties.getBooleanProperty("idea.trace.stub.index.update", false);
1801
1802   @ApiStatus.Internal
1803   static <K, V> int getIndexExtensionVersion(@NotNull FileBasedIndexExtension<K, V> extension) {
1804     int version = extension.getVersion();
1805
1806     if (VfsAwareMapReduceIndex.hasSnapshotMapping(extension)) {
1807       version += SnapshotInputMappings.getVersion();
1808     }
1809     return version;
1810   }
1811 }