b5ae17f8f67459cce3b0e72015d31a39b522e6b3
[idea/community.git] / platform / lang-impl / src / com / intellij / util / indexing / FileBasedIndexImpl.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.util.indexing;
18
19 import com.intellij.AppTopics;
20 import com.intellij.concurrency.JobLauncher;
21 import com.intellij.history.LocalHistory;
22 import com.intellij.ide.plugins.PluginManager;
23 import com.intellij.ide.util.DelegatingProgressIndicator;
24 import com.intellij.lang.ASTNode;
25 import com.intellij.notification.NotificationDisplayType;
26 import com.intellij.notification.NotificationGroup;
27 import com.intellij.notification.NotificationType;
28 import com.intellij.openapi.actionSystem.ex.ActionUtil;
29 import com.intellij.openapi.application.ApplicationAdapter;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.application.ModalityState;
32 import com.intellij.openapi.application.PathManager;
33 import com.intellij.openapi.diagnostic.Logger;
34 import com.intellij.openapi.editor.Document;
35 import com.intellij.openapi.editor.impl.EditorHighlighterCache;
36 import com.intellij.openapi.extensions.Extensions;
37 import com.intellij.openapi.fileEditor.FileDocumentManager;
38 import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
39 import com.intellij.openapi.fileTypes.*;
40 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
41 import com.intellij.openapi.module.Module;
42 import com.intellij.openapi.module.ModuleManager;
43 import com.intellij.openapi.progress.ProcessCanceledException;
44 import com.intellij.openapi.progress.ProgressIndicator;
45 import com.intellij.openapi.progress.ProgressManager;
46 import com.intellij.openapi.progress.Task;
47 import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
48 import com.intellij.openapi.project.*;
49 import com.intellij.openapi.roots.*;
50 import com.intellij.openapi.util.*;
51 import com.intellij.openapi.util.io.FileUtil;
52 import com.intellij.openapi.util.registry.Registry;
53 import com.intellij.openapi.vfs.*;
54 import com.intellij.openapi.vfs.impl.BulkVirtualFileListenerAdapter;
55 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
56 import com.intellij.openapi.vfs.newvfs.ManagingFS;
57 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
58 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
59 import com.intellij.openapi.vfs.newvfs.impl.VirtualFileSystemEntry;
60 import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
61 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
62 import com.intellij.psi.*;
63 import com.intellij.psi.impl.PsiDocumentTransactionListener;
64 import com.intellij.psi.impl.PsiManagerImpl;
65 import com.intellij.psi.impl.PsiTreeChangeEventImpl;
66 import com.intellij.psi.impl.PsiTreeChangePreprocessor;
67 import com.intellij.psi.impl.cache.impl.id.IdIndex;
68 import com.intellij.psi.impl.cache.impl.id.PlatformIdTableBuilding;
69 import com.intellij.psi.impl.source.PsiFileImpl;
70 import com.intellij.psi.search.EverythingGlobalScope;
71 import com.intellij.psi.search.GlobalSearchScope;
72 import com.intellij.psi.stubs.SerializationManagerEx;
73 import com.intellij.util.*;
74 import com.intellij.util.concurrency.Semaphore;
75 import com.intellij.util.containers.ContainerUtil;
76 import com.intellij.util.io.*;
77 import com.intellij.util.io.DataOutputStream;
78 import com.intellij.util.io.storage.HeavyProcessLatch;
79 import com.intellij.util.messages.MessageBus;
80 import com.intellij.util.messages.MessageBusConnection;
81 import gnu.trove.*;
82 import jsr166e.extra.SequenceLock;
83 import org.jetbrains.annotations.NotNull;
84 import org.jetbrains.annotations.Nullable;
85
86 import java.io.*;
87 import java.lang.ref.SoftReference;
88 import java.lang.ref.WeakReference;
89 import java.util.*;
90 import java.util.concurrent.ConcurrentLinkedQueue;
91 import java.util.concurrent.ScheduledFuture;
92 import java.util.concurrent.atomic.AtomicBoolean;
93 import java.util.concurrent.atomic.AtomicInteger;
94 import java.util.concurrent.atomic.AtomicReference;
95 import java.util.concurrent.locks.Lock;
96
97 /**
98  * @author Eugene Zhuravlev
99  * @since Dec 20, 2007
100  */
101 public class FileBasedIndexImpl extends FileBasedIndex {
102   private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.FileBasedIndexImpl");
103   private static final String CORRUPTION_MARKER_NAME = "corruption.marker";
104   private static final NotificationGroup NOTIFICATIONS = new NotificationGroup("Indexing", NotificationDisplayType.BALLOON, false);
105
106   private final Map<ID<?, ?>, Pair<UpdatableIndex<?, ?, FileContent>, InputFilter>> myIndices =
107     new THashMap<ID<?, ?>, Pair<UpdatableIndex<?, ?, FileContent>, InputFilter>>();
108   private final List<ID<?, ?>> myIndicesWithoutFileTypeInfo = new ArrayList<ID<?, ?>>();
109   private final Map<FileType, List<ID<?, ?>>> myFileType2IndicesWithFileTypeInfoMap = new THashMap<FileType, List<ID<?, ?>>>();
110   private final List<ID<?, ?>> myIndicesForDirectories = new SmartList<ID<?, ?>>();
111
112   private final Map<ID<?, ?>, Semaphore> myUnsavedDataIndexingSemaphores = new THashMap<ID<?, ?>, Semaphore>();
113   private final TObjectIntHashMap<ID<?, ?>> myIndexIdToVersionMap = new TObjectIntHashMap<ID<?, ?>>();
114   private final Set<ID<?, ?>> myNotRequiringContentIndices = new THashSet<ID<?, ?>>();
115   private final Set<ID<?, ?>> myRequiringContentIndices = new THashSet<ID<?, ?>>();
116   private final Set<ID<?, ?>> myPsiDependentIndices = new THashSet<ID<?, ?>>();
117   private final Set<FileType> myNoLimitCheckTypes = new THashSet<FileType>();
118
119   private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap();
120   @NotNull private final ChangedFilesCollector myChangedFilesCollector;
121
122   private final List<IndexableFileSet> myIndexableSets = ContainerUtil.createLockFreeCopyOnWriteList();
123   private final Map<IndexableFileSet, Project> myIndexableSetToProjectMap = new THashMap<IndexableFileSet, Project>();
124
125   private static final int OK = 1;
126   private static final int REQUIRES_REBUILD = 2;
127   private static final int REBUILD_IN_PROGRESS = 3;
128   private static final Map<ID<?, ?>, AtomicInteger> ourRebuildStatus = new THashMap<ID<?, ?>, AtomicInteger>();
129
130   private final MessageBusConnection myConnection;
131   private final FileDocumentManager myFileDocumentManager;
132   private final FileTypeManagerImpl myFileTypeManager;
133   private final SerializationManagerEx mySerializationManagerEx;
134   private final Set<ID<?, ?>> myUpToDateIndicesForUnsavedOrTransactedDocuments = ContainerUtil.newConcurrentSet();
135   private volatile SmartFMap<Document, PsiFile> myTransactionMap = SmartFMap.emptyMap();
136
137   @Nullable private final String myConfigPath;
138   @Nullable private final String myLogPath;
139   private final boolean myIsUnitTestMode;
140   @Nullable private ScheduledFuture<?> myFlushingFuture;
141   private volatile int myLocalModCount;
142   private volatile int myFilesModCount;
143   private final AtomicInteger myUpdatingFiles = new AtomicInteger();
144   private final Set<Project> myProjectsBeingUpdated = ContainerUtil.newConcurrentSet();
145
146   @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"}) private volatile boolean myInitialized;
147     // need this variable for memory barrier
148
149   public FileBasedIndexImpl(@SuppressWarnings("UnusedParameters") VirtualFileManager vfManager,
150                             FileDocumentManager fdm,
151                             FileTypeManagerImpl fileTypeManager,
152                             @NotNull MessageBus bus,
153                             SerializationManagerEx sm) {
154     myFileDocumentManager = fdm;
155     myFileTypeManager = fileTypeManager;
156     mySerializationManagerEx = sm;
157     myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
158     myConfigPath = calcConfigPath(PathManager.getConfigPath());
159     myLogPath = calcConfigPath(PathManager.getLogPath());
160
161     final MessageBusConnection connection = bus.connect();
162     connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() {
163       @Override
164       public void transactionStarted(@NotNull final Document doc, @NotNull final PsiFile file) {
165         myTransactionMap = myTransactionMap.plus(doc, file);
166         myUpToDateIndicesForUnsavedOrTransactedDocuments.clear();
167       }
168
169       @Override
170       public void transactionCompleted(@NotNull final Document doc, @NotNull final PsiFile file) {
171         myTransactionMap = myTransactionMap.minus(doc);
172       }
173     });
174
175     connection.subscribe(FileTypeManager.TOPIC, new FileTypeListener() {
176       @Nullable private Map<FileType, Set<String>> myTypeToExtensionMap;
177
178       @Override
179       public void beforeFileTypesChanged(@NotNull final FileTypeEvent event) {
180         cleanupProcessedFlag();
181         myTypeToExtensionMap = new THashMap<FileType, Set<String>>();
182         for (FileType type : myFileTypeManager.getRegisteredFileTypes()) {
183           myTypeToExtensionMap.put(type, getExtensions(type));
184         }
185       }
186
187       @Override
188       public void fileTypesChanged(@NotNull final FileTypeEvent event) {
189         final Map<FileType, Set<String>> oldExtensions = myTypeToExtensionMap;
190         myTypeToExtensionMap = null;
191         if (oldExtensions != null) {
192           final Map<FileType, Set<String>> newExtensions = new THashMap<FileType, Set<String>>();
193           for (FileType type : myFileTypeManager.getRegisteredFileTypes()) {
194             newExtensions.put(type, getExtensions(type));
195           }
196           // we are interested only in extension changes or removals.
197           // addition of an extension is handled separately by RootsChanged event
198           if (!newExtensions.keySet().containsAll(oldExtensions.keySet())) {
199             rebuildAllIndices();
200             return;
201           }
202           for (Map.Entry<FileType, Set<String>> entry : oldExtensions.entrySet()) {
203             FileType fileType = entry.getKey();
204             Set<String> strings = entry.getValue();
205             if (!newExtensions.get(fileType).containsAll(strings)) {
206               rebuildAllIndices();
207               return;
208             }
209           }
210         }
211       }
212
213       @NotNull
214       private Set<String> getExtensions(@NotNull FileType type) {
215         final Set<String> set = new THashSet<String>();
216         for (FileNameMatcher matcher : myFileTypeManager.getAssociations(type)) {
217           set.add(matcher.getPresentableString());
218         }
219         return set;
220       }
221
222       private void rebuildAllIndices() {
223         IndexingStamp.flushCaches();
224         for (ID<?, ?> indexId : myIndices.keySet()) {
225           try {
226             clearIndex(indexId);
227           }
228           catch (StorageException e) {
229             LOG.info(e);
230           }
231         }
232         scheduleIndexRebuild();
233       }
234     });
235
236     connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
237       @Override
238       public void fileContentReloaded(@NotNull VirtualFile file, @NotNull Document document) {
239         cleanupMemoryStorage();
240       }
241
242       @Override
243       public void unsavedDocumentsDropped() {
244         cleanupMemoryStorage();
245       }
246     });
247
248     ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() {
249       @Override
250       public void writeActionStarted(Object action) {
251         myUpToDateIndicesForUnsavedOrTransactedDocuments.clear();
252       }
253     });
254
255     myChangedFilesCollector = new ChangedFilesCollector();
256     myConnection = connection;
257   }
258
259   public static boolean isProjectOrWorkspaceFile(@NotNull VirtualFile file, @Nullable FileType fileType) {
260     return ProjectCoreUtil.isProjectOrWorkspaceFile(file, fileType);
261   }
262
263   @Override
264   public void requestReindex(@NotNull final VirtualFile file) {
265     myChangedFilesCollector.invalidateIndices(file, true);
266   }
267
268   private void initExtensions() {
269     try {
270       File indexRoot = PathManager.getIndexRoot();
271       final File corruptionMarker = new File(indexRoot, CORRUPTION_MARKER_NAME);
272       final boolean currentVersionCorrupted = corruptionMarker.exists();
273       if (currentVersionCorrupted) {
274         FileUtil.deleteWithRenaming(indexRoot);
275         indexRoot.mkdirs();
276       }
277
278       FileBasedIndexExtension[] extensions = Extensions.getExtensions(FileBasedIndexExtension.EXTENSION_POINT_NAME);
279
280       boolean versionChanged = false;
281       for (FileBasedIndexExtension<?, ?> extension : extensions) {
282         try {
283           ourRebuildStatus.put(extension.getName(), new AtomicInteger(OK));
284           versionChanged |= registerIndexer(extension);
285         }
286         catch (IOException e) {
287           throw e;
288         }
289         catch (Throwable t) {
290           PluginManager.handleComponentError(t, extension.getClass().getName(), null);
291         }
292       }
293
294       for (List<ID<?, ?>> value : myFileType2IndicesWithFileTypeInfoMap.values()) {
295         value.addAll(myIndicesWithoutFileTypeInfo);
296       }
297       FileUtil.delete(corruptionMarker);
298
299       String rebuildNotification = null;
300       if (currentVersionCorrupted) {
301         rebuildNotification = "Index files on disk are corrupted. Indices will be rebuilt.";
302       }
303       else if (versionChanged) {
304         rebuildNotification = "Index file format has changed for some indices. These indices will be rebuilt.";
305       }
306       if (rebuildNotification != null
307           && !ApplicationManager.getApplication().isHeadlessEnvironment()
308           && Registry.is("ide.showIndexRebuildMessage")) {
309         NOTIFICATIONS.createNotification("Index Rebuild", rebuildNotification, NotificationType.INFORMATION, null).notify(null);
310       }
311
312       dropUnregisteredIndices();
313
314       // check if rebuild was requested for any index during registration
315       for (ID<?, ?> indexId : myIndices.keySet()) {
316         if (ourRebuildStatus.get(indexId).compareAndSet(REQUIRES_REBUILD, OK)) {
317           try {
318             clearIndex(indexId);
319           }
320           catch (StorageException e) {
321             requestRebuild(indexId);
322             LOG.error(e);
323           }
324         }
325       }
326
327       myConnection.subscribe(VirtualFileManager.VFS_CHANGES, myChangedFilesCollector);
328
329       registerIndexableSet(new AdditionalIndexableFileSet(), null);
330     }
331     catch (IOException e) {
332       throw new RuntimeException(e);
333     }
334     finally {
335       ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
336         @Override
337         public void run() {
338           performShutdown();
339         }
340       });
341       saveRegisteredIndices(myIndices.keySet());
342       myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() {
343         private int lastModCount = 0;
344
345         @Override
346         public void run() {
347           if (lastModCount == myLocalModCount) {
348             flushAllIndices(lastModCount);
349           }
350           lastModCount = myLocalModCount;
351         }
352       });
353       myInitialized = true; // this will ensure that all changes to component's state will be visible to other threads
354     }
355   }
356
357   @Override
358   public void initComponent() {
359     initExtensions();
360   }
361
362   @Nullable
363   private static String calcConfigPath(@NotNull String path) {
364     try {
365       final String _path = FileUtil.toSystemIndependentName(new File(path).getCanonicalPath());
366       return _path.endsWith("/") ? _path : _path + "/";
367     }
368     catch (IOException e) {
369       LOG.info(e);
370       return null;
371     }
372   }
373
374   /**
375    * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted
376    */
377   private <K, V> boolean registerIndexer(@NotNull final FileBasedIndexExtension<K, V> extension) throws IOException {
378     final ID<K, V> name = extension.getName();
379     final int version = extension.getVersion();
380     final File versionFile = IndexInfrastructure.getVersionFile(name);
381     final boolean versionFileExisted = versionFile.exists();
382     boolean versionChanged = false;
383     if (IndexingStamp.versionDiffers(versionFile, version)) {
384       if (versionFileExisted) {
385         versionChanged = true;
386         LOG.info("Version has changed for index " + name + ". The index will be rebuilt.");
387       }
388       if (extension.hasSnapshotMapping() && versionChanged) {
389         FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name));
390       }
391       File rootDir = IndexInfrastructure.getIndexRootDir(name);
392       if (versionFileExisted) FileUtil.deleteWithRenaming(rootDir);
393       IndexingStamp.rewriteVersion(versionFile, version);
394     }
395
396     initIndexStorage(extension, version, versionFile);
397
398     return versionChanged;
399   }
400
401   private <K, V> void initIndexStorage(@NotNull FileBasedIndexExtension<K, V> extension, int version, @NotNull File versionFile)
402     throws IOException {
403     MapIndexStorage<K, V> storage = null;
404     final ID<K, V> name = extension.getName();
405     boolean contentHashesEnumeratorOk = false;
406
407     for (int attempt = 0; attempt < 2; attempt++) {
408       try {
409         if (extension.hasSnapshotMapping()) {
410           ContentHashesSupport.initContentHashesEnumerator();
411           contentHashesEnumeratorOk = true;
412         }
413         storage = new MapIndexStorage<K, V>(
414           IndexInfrastructure.getStorageFile(name),
415           extension.getKeyDescriptor(),
416           extension.getValueExternalizer(),
417           extension.getCacheSize(),
418           extension.keyIsUniqueForIndexedFile(),
419           extension.traceKeyHashToVirtualFileMapping()
420         );
421
422         final MemoryIndexStorage<K, V> memStorage = new MemoryIndexStorage<K, V>(storage);
423         final UpdatableIndex<K, V, FileContent> index = createIndex(name, extension, memStorage);
424         final InputFilter inputFilter = extension.getInputFilter();
425
426         myIndices.put(name, new Pair<UpdatableIndex<?, ?, FileContent>, InputFilter>(index, new IndexableFilesFilter(inputFilter)));
427         if (inputFilter instanceof FileTypeSpecificInputFilter) {
428           ((FileTypeSpecificInputFilter)inputFilter).registerFileTypesUsedForIndexing(new Consumer<FileType>() {
429             final Set<FileType> addedTypes = new THashSet<FileType>();
430             @Override
431             public void consume(FileType type) {
432               if (type == null || !addedTypes.add(type)) {
433                 return;
434               }
435               List<ID<?, ?>> ids = myFileType2IndicesWithFileTypeInfoMap.get(type);
436               if (ids == null) myFileType2IndicesWithFileTypeInfoMap.put(type, ids = new ArrayList<ID<?, ?>>(5));
437               ids.add(name);
438             }
439           });
440         }
441         else {
442           myIndicesWithoutFileTypeInfo.add(name);
443         }
444
445         myUnsavedDataIndexingSemaphores.put(name, new Semaphore());
446         myIndexIdToVersionMap.put(name, version);
447         if (!extension.dependsOnFileContent()) {
448           if (extension.indexDirectories()) myIndicesForDirectories.add(name);
449           myNotRequiringContentIndices.add(name);
450         }
451         else {
452           myRequiringContentIndices.add(name);
453         }
454         if (extension instanceof PsiDependentIndex) myPsiDependentIndices.add(name);
455         myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable());
456         break;
457       }
458       catch (Exception e) {
459         LOG.info(e);
460         boolean instantiatedStorage = storage != null;
461         try {
462           if (storage != null) storage.close();
463           storage = null;
464         }
465         catch (Exception ignored) {
466         }
467
468         FileUtil.deleteWithRenaming(IndexInfrastructure.getIndexRootDir(name));
469
470         if (extension.hasSnapshotMapping() && (!contentHashesEnumeratorOk || instantiatedStorage)) {
471           FileUtil.deleteWithRenaming(IndexInfrastructure.getPersistentIndexRootDir(name)); // todo there is possibility of corruption of storage and content hashes
472         }
473         IndexingStamp.rewriteVersion(versionFile, version);
474       }
475     }
476   }
477
478   private static void saveRegisteredIndices(@NotNull Collection<ID<?, ?>> ids) {
479     final File file = getRegisteredIndicesFile();
480     try {
481       FileUtil.createIfDoesntExist(file);
482       final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
483       try {
484         os.writeInt(ids.size());
485         for (ID<?, ?> id : ids) {
486           IOUtil.writeString(id.toString(), os);
487         }
488       }
489       finally {
490         os.close();
491       }
492     }
493     catch (IOException ignored) {
494     }
495   }
496
497   @NotNull
498   private static Set<String> readRegisteredIndexNames() {
499     final Set<String> result = new THashSet<String>();
500     try {
501       final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile())));
502       try {
503         final int size = in.readInt();
504         for (int idx = 0; idx < size; idx++) {
505           result.add(IOUtil.readString(in));
506         }
507       }
508       finally {
509         in.close();
510       }
511     }
512     catch (IOException ignored) {
513     }
514     return result;
515   }
516
517   @NotNull
518   private static File getRegisteredIndicesFile() {
519     return new File(PathManager.getIndexRoot(), "registered");
520   }
521
522   @NotNull
523   private <K, V> UpdatableIndex<K, V, FileContent> createIndex(@NotNull final ID<K, V> indexId,
524                                                                @NotNull final FileBasedIndexExtension<K, V> extension,
525                                                                @NotNull final MemoryIndexStorage<K, V> storage)
526     throws StorageException, IOException {
527     final MapReduceIndex<K, V, FileContent> index;
528     if (extension instanceof CustomImplementationFileBasedIndexExtension) {
529       final UpdatableIndex<K, V, FileContent> custom =
530         ((CustomImplementationFileBasedIndexExtension<K, V, FileContent>)extension).createIndexImplementation(indexId, this, storage);
531       if (!(custom instanceof MapReduceIndex)) {
532         return custom;
533       }
534       index = (MapReduceIndex<K, V, FileContent>)custom;
535     }
536     else {
537       DataExternalizer<Collection<K>> externalizer =
538         extension.hasSnapshotMapping() && IdIndex.ourSnapshotMappingsEnabled
539         ? createInputsIndexExternalizer(extension, indexId, extension.getKeyDescriptor())
540         : null;
541       index = new MapReduceIndex<K, V, FileContent>(
542         indexId, extension.getIndexer(), storage, externalizer, extension.getValueExternalizer(), extension instanceof PsiDependentIndex);
543     }
544     index.setInputIdToDataKeysIndex(new Factory<PersistentHashMap<Integer, Collection<K>>>() {
545       @Override
546       public PersistentHashMap<Integer, Collection<K>> create() {
547         try {
548           return createIdToDataKeysIndex(extension, storage);
549         }
550         catch (IOException e) {
551           throw new RuntimeException(e);
552         }
553       }
554     });
555
556     return index;
557   }
558
559   @NotNull
560   public static <K> PersistentHashMap<Integer, Collection<K>> createIdToDataKeysIndex(@NotNull FileBasedIndexExtension <K, ?> extension,
561                                                                                       @NotNull MemoryIndexStorage<K, ?> storage)
562     throws IOException {
563     ID<K, ?> indexId = extension.getName();
564     KeyDescriptor<K> keyDescriptor = extension.getKeyDescriptor();
565     final File indexStorageFile = IndexInfrastructure.getInputIndexStorageFile(indexId);
566     final AtomicBoolean isBufferingMode = new AtomicBoolean();
567     final TIntObjectHashMap<Collection<K>> tempMap = new TIntObjectHashMap<Collection<K>>();
568
569     // Important! Update IdToDataKeysIndex depending on the sate of "buffering" flag from the MemoryStorage.
570     // If buffering is on, all changes should be done in memory (similar to the way it is done in memory storage).
571     // 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
572     // wrong sets of keys for the given file. This will lead to unpredictable results in main index because it will not be
573     // cleared properly before updating (removed data will still be present on disk). See IDEA-52223 for illustration of possible effects.
574
575     final PersistentHashMap<Integer, Collection<K>> map = new PersistentHashMap<Integer, Collection<K>>(
576       indexStorageFile, EnumeratorIntegerDescriptor.INSTANCE, createInputsIndexExternalizer(extension, indexId, keyDescriptor)
577     ) {
578
579       @Override
580       protected Collection<K> doGet(Integer integer) throws IOException {
581         if (isBufferingMode.get()) {
582           final Collection<K> collection = tempMap.get(integer);
583           if (collection != null) {
584             return collection;
585           }
586         }
587         return super.doGet(integer);
588       }
589
590       @Override
591       protected void doPut(Integer integer, @Nullable Collection<K> ks) throws IOException {
592         if (isBufferingMode.get()) {
593           tempMap.put(integer, ks == null ? Collections.<K>emptySet() : ks);
594         }
595         else {
596           super.doPut(integer, ks);
597         }
598       }
599
600       @Override
601       protected void doRemove(Integer integer) throws IOException {
602         if (isBufferingMode.get()) {
603           tempMap.put(integer, Collections.<K>emptySet());
604         }
605         else {
606           super.doRemove(integer);
607         }
608       }
609     };
610
611     storage.addBufferingStateListener(new MemoryIndexStorage.BufferingStateListener() {
612       @Override
613       public void bufferingStateChanged(boolean newState) {
614         synchronized (map) {
615           isBufferingMode.set(newState);
616         }
617       }
618
619       @Override
620       public void memoryStorageCleared() {
621         synchronized (map) {
622           tempMap.clear();
623         }
624       }
625     });
626     return map;
627   }
628
629   private static <K> DataExternalizer<Collection<K>> createInputsIndexExternalizer(FileBasedIndexExtension<K, ?> extension,
630                                                                                   ID<K, ?> indexId,
631                                                                                   KeyDescriptor<K> keyDescriptor) {
632     DataExternalizer<Collection<K>> externalizer;
633     if (extension instanceof CustomInputsIndexFileBasedIndexExtension) {
634       externalizer = ((CustomInputsIndexFileBasedIndexExtension<K>)extension).createExternalizer();
635     } else {
636       externalizer = new InputIndexDataExternalizer<K>(keyDescriptor, indexId);
637     }
638     return externalizer;
639   }
640
641   @Override
642   public void disposeComponent() {
643     performShutdown();
644   }
645
646   private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(false);
647
648   private void performShutdown() {
649     if (!myShutdownPerformed.compareAndSet(false, true)) {
650       return; // already shut down
651     }
652     try {
653       if (myFlushingFuture != null) {
654         myFlushingFuture.cancel(false);
655         myFlushingFuture = null;
656       }
657
658       //myFileDocumentManager.saveAllDocuments(); // rev=Eugene Juravlev
659     }
660     finally {
661       LOG.info("START INDEX SHUTDOWN");
662       try {
663         myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
664         IndexingStamp.flushCaches();
665
666         for (ID<?, ?> indexId : myIndices.keySet()) {
667           final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
668           assert index != null;
669           checkRebuild(indexId, true); // if the index was scheduled for rebuild, only clean it
670           index.dispose();
671         }
672
673         ContentHashesSupport.flushContentHashes();
674         myConnection.disconnect();
675       }
676       catch (Throwable e) {
677         LOG.error("Problems during index shutdown", e);
678       }
679       LOG.info("END INDEX SHUTDOWN");
680     }
681   }
682
683   private void flushAllIndices(final long modCount) {
684     if (HeavyProcessLatch.INSTANCE.isRunning()) {
685       return;
686     }
687     IndexingStamp.flushCaches();
688     for (ID<?, ?> indexId : new ArrayList<ID<?, ?>>(myIndices.keySet())) {
689       if (HeavyProcessLatch.INSTANCE.isRunning() || modCount != myLocalModCount) {
690         return; // do not interfere with 'main' jobs
691       }
692       try {
693         final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
694         if (index != null) {
695           index.flush();
696         }
697       }
698       catch (StorageException e) {
699         LOG.info(e);
700         requestRebuild(indexId);
701       }
702     }
703
704     if (!HeavyProcessLatch.INSTANCE.isRunning() && modCount == myLocalModCount) { // do not interfere with 'main' jobs
705       mySerializationManagerEx.flushNameStorage();
706     }
707     ContentHashesSupport.flushContentHashes();
708   }
709
710   @Override
711   @NotNull
712   public <K> Collection<K> getAllKeys(@NotNull final ID<K, ?> indexId, @NotNull Project project) {
713     Set<K> allKeys = new THashSet<K>();
714     processAllKeys(indexId, new CommonProcessors.CollectProcessor<K>(allKeys), project);
715     return allKeys;
716   }
717
718   @Override
719   public <K> boolean processAllKeys(@NotNull final ID<K, ?> indexId, @NotNull Processor<K> processor, @Nullable Project project) {
720     return processAllKeys(indexId, processor, project == null ? new EverythingGlobalScope() : GlobalSearchScope.allScope(project), null);
721   }
722
723   @Override
724   public <K> boolean processAllKeys(@NotNull ID<K, ?> indexId, @NotNull Processor<K> processor, @NotNull GlobalSearchScope scope, @Nullable IdFilter idFilter) {
725     try {
726       final UpdatableIndex<K, ?, FileContent> index = getIndex(indexId);
727       if (index == null) {
728         return true;
729       }
730       ensureUpToDate(indexId, scope.getProject(), scope);
731       return index.processAllKeys(processor, scope, idFilter);
732     }
733     catch (StorageException e) {
734       scheduleRebuild(indexId, e);
735     }
736     catch (RuntimeException e) {
737       final Throwable cause = e.getCause();
738       if (cause instanceof StorageException || cause instanceof IOException) {
739         scheduleRebuild(indexId, cause);
740       }
741       else {
742         throw e;
743       }
744     }
745
746     return false;
747   }
748
749   private static final ThreadLocal<Integer> myUpToDateCheckState = new ThreadLocal<Integer>();
750
751   public static void disableUpToDateCheckForCurrentThread() {
752     final Integer currentValue = myUpToDateCheckState.get();
753     myUpToDateCheckState.set(currentValue == null ? 1 : currentValue.intValue() + 1);
754   }
755
756   public static void enableUpToDateCheckForCurrentThread() {
757     final Integer currentValue = myUpToDateCheckState.get();
758     if (currentValue != null) {
759       final int newValue = currentValue.intValue() - 1;
760       if (newValue != 0) {
761         myUpToDateCheckState.set(newValue);
762       }
763       else {
764         myUpToDateCheckState.remove();
765       }
766     }
767   }
768
769   private static boolean isUpToDateCheckEnabled() {
770     final Integer value = myUpToDateCheckState.get();
771     return value == null || value.intValue() == 0;
772   }
773
774
775   private final ThreadLocal<Boolean> myReentrancyGuard = new ThreadLocal<Boolean>() {
776     @Override
777     protected Boolean initialValue() {
778       return Boolean.FALSE;
779     }
780   };
781
782   /**
783    * DO NOT CALL DIRECTLY IN CLIENT CODE
784    * The method is internal to indexing engine end is called internally. The method is public due to implementation details
785    */
786   @Override
787   public <K> void ensureUpToDate(@NotNull final ID<K, ?> indexId, @Nullable Project project, @Nullable GlobalSearchScope filter) {
788     ensureUpToDate(indexId, project, filter, null);
789   }
790
791   protected <K> void ensureUpToDate(@NotNull final ID<K, ?> indexId,
792                                     @Nullable Project project,
793                                     @Nullable GlobalSearchScope filter,
794                                     @Nullable VirtualFile restrictedFile) {
795     ProgressManager.checkCanceled();
796     myContentlessIndicesUpdateQueue.ensureUpToDate(); // some content full indices depends on contentless ones
797     if (!needsFileContentLoading(indexId)) {
798       return; //indexed eagerly in foreground while building unindexed file list
799     }
800     if (filter == GlobalSearchScope.EMPTY_SCOPE) {
801       return;
802     }
803     if (ActionUtil.isDumbMode(project)) {
804       handleDumbMode(project);
805     }
806
807     if (myReentrancyGuard.get().booleanValue()) {
808       //assert false : "ensureUpToDate() is not reentrant!";
809       return;
810     }
811     myReentrancyGuard.set(Boolean.TRUE);
812
813     try {
814       myChangedFilesCollector.tryToEnsureAllInvalidateTasksCompleted();
815       if (isUpToDateCheckEnabled()) {
816         try {
817           checkRebuild(indexId, false);
818           myChangedFilesCollector.forceUpdate(project, filter, restrictedFile);
819           indexUnsavedDocuments(indexId, project, filter, restrictedFile);
820         }
821         catch (StorageException e) {
822           scheduleRebuild(indexId, e);
823         }
824         catch (RuntimeException e) {
825           final Throwable cause = e.getCause();
826           if (cause instanceof StorageException || cause instanceof IOException) {
827             scheduleRebuild(indexId, e);
828           }
829           else {
830             throw e;
831           }
832         }
833       }
834     }
835     finally {
836       myReentrancyGuard.set(Boolean.FALSE);
837     }
838   }
839
840   private static void handleDumbMode(@Nullable Project project) {
841     ProgressManager.checkCanceled(); // DumbModeAction.CANCEL
842
843     if (project != null) {
844       final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
845       if (progressIndicator instanceof BackgroundableProcessIndicator) {
846         final BackgroundableProcessIndicator indicator = (BackgroundableProcessIndicator)progressIndicator;
847         if (indicator.getDumbModeAction() == DumbModeAction.WAIT) {
848           assert !ApplicationManager.getApplication().isDispatchThread();
849           DumbService.getInstance(project).waitForSmartMode();
850           return;
851         }
852       }
853     }
854
855     throw new IndexNotReadyException();
856   }
857
858   @Override
859   @NotNull
860   public <K, V> List<V> getValues(@NotNull final ID<K, V> indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) {
861     final List<V> values = new SmartList<V>();
862     processValuesImpl(indexId, dataKey, true, null, new ValueProcessor<V>() {
863       @Override
864       public boolean process(final VirtualFile file, final V value) {
865         values.add(value);
866         return true;
867       }
868     }, filter, null);
869     return values;
870   }
871
872   @Override
873   @NotNull
874   public <K, V> Collection<VirtualFile> getContainingFiles(@NotNull final ID<K, V> indexId,
875                                                            @NotNull K dataKey,
876                                                            @NotNull final GlobalSearchScope filter) {
877     final Set<VirtualFile> files = new THashSet<VirtualFile>();
878     processValuesImpl(indexId, dataKey, false, null, new ValueProcessor<V>() {
879       @Override
880       public boolean process(final VirtualFile file, final V value) {
881         files.add(file);
882         return true;
883       }
884     }, filter, null);
885     return files;
886   }
887
888
889   @Override
890   public <K, V> boolean processValues(@NotNull final ID<K, V> indexId, @NotNull final K dataKey, @Nullable final VirtualFile inFile,
891                                       @NotNull ValueProcessor<V> processor, @NotNull final GlobalSearchScope filter) {
892     return processValues(indexId, dataKey, inFile, processor, filter, null);
893   }
894
895   @Override
896   public <K, V> boolean processValues(@NotNull ID<K, V> indexId,
897                                       @NotNull K dataKey,
898                                       @Nullable VirtualFile inFile,
899                                       @NotNull ValueProcessor<V> processor,
900                                       @NotNull GlobalSearchScope filter,
901                                       @Nullable IdFilter idFilter) {
902     return processValuesImpl(indexId, dataKey, false, inFile, processor, filter, idFilter);
903   }
904
905   @Nullable
906   private <K, V, R> R processExceptions(@NotNull final ID<K, V> indexId,
907                                         @Nullable final VirtualFile restrictToFile,
908                                         @NotNull final GlobalSearchScope filter,
909                                         @NotNull ThrowableConvertor<UpdatableIndex<K, V, FileContent>, R, StorageException> computable) {
910     try {
911       final UpdatableIndex<K, V, FileContent> index = getIndex(indexId);
912       if (index == null) {
913         return null;
914       }
915       final Project project = filter.getProject();
916       //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries";
917       ensureUpToDate(indexId, project, filter, restrictToFile);
918
919       try {
920         index.getReadLock().lock();
921         return computable.convert(index);
922       }
923       finally {
924         index.getReadLock().unlock();
925       }
926     }
927     catch (StorageException e) {
928       scheduleRebuild(indexId, e);
929     }
930     catch (RuntimeException e) {
931       final Throwable cause = getCauseToRebuildIndex(e);
932       if (cause != null) {
933         scheduleRebuild(indexId, cause);
934       }
935       else {
936         throw e;
937       }
938     } catch(AssertionError ae) {
939       scheduleRebuild(indexId, ae);
940     }
941     return null;
942   }
943
944   private <K, V> boolean processValuesImpl(@NotNull final ID<K, V> indexId, @NotNull final K dataKey, final boolean ensureValueProcessedOnce,
945                                            @Nullable final VirtualFile restrictToFile, @NotNull final ValueProcessor<V> processor,
946                                            @NotNull final GlobalSearchScope scope, @Nullable final IdFilter idFilter) {
947     ThrowableConvertor<UpdatableIndex<K, V, FileContent>, Boolean, StorageException> keyProcessor =
948       new ThrowableConvertor<UpdatableIndex<K, V, FileContent>, Boolean, StorageException>() {
949         @Override
950         public Boolean convert(@NotNull UpdatableIndex<K, V, FileContent> index) throws StorageException {
951           final ValueContainer<V> container = index.getData(dataKey);
952
953           boolean shouldContinue = true;
954
955           if (restrictToFile != null) {
956             if (restrictToFile instanceof VirtualFileWithId) {
957               final int restrictedFileId = getFileId(restrictToFile);
958               for (final ValueContainer.ValueIterator<V> valueIt = container.getValueIterator(); valueIt.hasNext(); ) {
959                 final V value = valueIt.next();
960                 if (valueIt.getValueAssociationPredicate().contains(restrictedFileId)) {
961                   shouldContinue = processor.process(restrictToFile, value);
962                   if (!shouldContinue) {
963                     break;
964                   }
965                 }
966               }
967             }
968           }
969           else {
970             final PersistentFS fs = (PersistentFS)ManagingFS.getInstance();
971             final IdFilter filter = idFilter != null ? idFilter : projectIndexableFiles(scope.getProject());
972             VALUES_LOOP:
973             for (final ValueContainer.ValueIterator<V> valueIt = container.getValueIterator(); valueIt.hasNext(); ) {
974               final V value = valueIt.next();
975               for (final ValueContainer.IntIterator inputIdsIterator = valueIt.getInputIdsIterator(); inputIdsIterator.hasNext(); ) {
976                 final int id = inputIdsIterator.next();
977                 if (filter != null && !filter.containsFileId(id)) continue;
978                 VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
979                 if (file != null && scope.accept(file)) {
980                   shouldContinue = processor.process(file, value);
981                   if (!shouldContinue) {
982                     break VALUES_LOOP;
983                   }
984                   if (ensureValueProcessedOnce) {
985                     break; // continue with the next value
986                   }
987                 }
988               }
989             }
990           }
991           return shouldContinue;
992         }
993       };
994     final Boolean result = processExceptions(indexId, restrictToFile, scope, keyProcessor);
995     return result == null || result.booleanValue();
996   }
997
998   @Override
999   public <K, V> boolean processFilesContainingAllKeys(@NotNull final ID<K, V> indexId,
1000                                                       @NotNull final Collection<K> dataKeys,
1001                                                       @NotNull final GlobalSearchScope filter,
1002                                                       @Nullable Condition<V> valueChecker,
1003                                                       @NotNull final Processor<VirtualFile> processor) {
1004     ProjectIndexableFilesFilter filesSet = projectIndexableFiles(filter.getProject());
1005     final TIntHashSet set = collectFileIdsContainingAllKeys(indexId, dataKeys, filter, valueChecker, filesSet);
1006     return set != null && processVirtualFiles(set, filter, processor);
1007   }
1008
1009   private static final Key<SoftReference<ProjectIndexableFilesFilter>> ourProjectFilesSetKey = Key.create("projectFiles");
1010
1011   public void filesUpdateEnumerationFinished() {
1012     myContentlessIndicesUpdateQueue.ensureUpToDate();
1013     myContentlessIndicesUpdateQueue.signalUpdateEnd();
1014   }
1015
1016   public static final class ProjectIndexableFilesFilter extends IdFilter {
1017     private static final int SHIFT = 6;
1018     private static final int MASK = (1 << SHIFT) - 1;
1019     private final long[] myBitMask;
1020     private final int myModificationCount;
1021     private final int myMinId;
1022     private final int myMaxId;
1023
1024     private ProjectIndexableFilesFilter(@NotNull TIntArrayList set, int modificationCount) {
1025       myModificationCount = modificationCount;
1026       final int[] minMax = new int[2];
1027       if (!set.isEmpty()) {
1028         minMax[0] = minMax[1] = set.get(0);
1029       }
1030       set.forEach(new TIntProcedure() {
1031         @Override
1032         public boolean execute(int value) {
1033           if (value < 0) value = -value;
1034           minMax[0] = Math.min(minMax[0], value);
1035           minMax[1] = Math.max(minMax[1], value);
1036           return true;
1037         }
1038       });
1039       myMaxId = minMax[1];
1040       myMinId = minMax[0];
1041       myBitMask = new long[((myMaxId - myMinId) >> SHIFT) + 1];
1042       set.forEach(new TIntProcedure() {
1043         @Override
1044         public boolean execute(int value) {
1045           if (value < 0) value = -value;
1046           value -= myMinId;
1047           myBitMask[value >> SHIFT] |= (1L << (value & MASK));
1048           return true;
1049         }
1050       });
1051     }
1052
1053     @Override
1054     public boolean containsFileId(int id) {
1055       if (id < myMinId) return false;
1056       if (id > myMaxId) return false;
1057       id -= myMinId;
1058       return (myBitMask[id >> SHIFT] & (1L << (id & MASK))) != 0;
1059     }
1060   }
1061
1062   void filesUpdateStarted(Project project) {
1063     myContentlessIndicesUpdateQueue.signalUpdateStart();
1064     myContentlessIndicesUpdateQueue.ensureUpToDate();
1065     myProjectsBeingUpdated.add(project);
1066   }
1067
1068   void filesUpdateFinished(@NotNull Project project) {
1069     myProjectsBeingUpdated.remove(project);
1070     ++myFilesModCount;
1071   }
1072
1073   private final Lock myCalcIndexableFilesLock = new SequenceLock();
1074
1075   @Nullable
1076   public ProjectIndexableFilesFilter projectIndexableFiles(@Nullable Project project) {
1077     if (project == null || myUpdatingFiles.get() > 0) return null;
1078     if (myProjectsBeingUpdated.contains(project)) return null;
1079
1080     SoftReference<ProjectIndexableFilesFilter> reference = project.getUserData(ourProjectFilesSetKey);
1081     ProjectIndexableFilesFilter data = com.intellij.reference.SoftReference.dereference(reference);
1082     if (data != null && data.myModificationCount == myFilesModCount) return data;
1083
1084     if (myCalcIndexableFilesLock.tryLock()) { // make best effort for calculating filter
1085       try {
1086         reference = project.getUserData(ourProjectFilesSetKey);
1087         data = com.intellij.reference.SoftReference.dereference(reference);
1088         if (data != null && data.myModificationCount == myFilesModCount) {
1089           return data;
1090         }
1091
1092         long start = System.currentTimeMillis();
1093
1094         final TIntArrayList filesSet = new TIntArrayList();
1095         iterateIndexableFiles(new ContentIterator() {
1096           @Override
1097           public boolean processFile(@NotNull VirtualFile fileOrDir) {
1098             filesSet.add(((VirtualFileWithId)fileOrDir).getId());
1099             return true;
1100           }
1101         }, project, SilentProgressIndicator.create());
1102         ProjectIndexableFilesFilter filter = new ProjectIndexableFilesFilter(filesSet, myFilesModCount);
1103         project.putUserData(ourProjectFilesSetKey, new SoftReference<ProjectIndexableFilesFilter>(filter));
1104
1105         long finish = System.currentTimeMillis();
1106         LOG.debug(filesSet.size() + " files iterated in " + (finish - start) + " ms");
1107
1108         return filter;
1109       }
1110       finally {
1111         myCalcIndexableFilesLock.unlock();
1112       }
1113     }
1114     return null; // ok, no filtering
1115   }
1116
1117   @Nullable
1118   private <K, V> TIntHashSet collectFileIdsContainingAllKeys(@NotNull final ID<K, V> indexId,
1119                                                              @NotNull final Collection<K> dataKeys,
1120                                                              @NotNull final GlobalSearchScope filter,
1121                                                              @Nullable final Condition<V> valueChecker,
1122                                                              @Nullable final ProjectIndexableFilesFilter projectFilesFilter) {
1123     final ThrowableConvertor<UpdatableIndex<K, V, FileContent>, TIntHashSet, StorageException> convertor =
1124       new ThrowableConvertor<UpdatableIndex<K, V, FileContent>, TIntHashSet, StorageException>() {
1125         @Nullable
1126         @Override
1127         public TIntHashSet convert(@NotNull UpdatableIndex<K, V, FileContent> index) throws StorageException {
1128           TIntHashSet mainIntersection = null;
1129
1130           for (K dataKey : dataKeys) {
1131             ProgressManager.checkCanceled();
1132             final TIntHashSet copy = new TIntHashSet();
1133             final ValueContainer<V> container = index.getData(dataKey);
1134
1135             for (final ValueContainer.ValueIterator<V> valueIt = container.getValueIterator(); valueIt.hasNext(); ) {
1136               final V value = valueIt.next();
1137               if (valueChecker != null && !valueChecker.value(value)) {
1138                 continue;
1139               }
1140
1141               ValueContainer.IntIterator iterator = valueIt.getInputIdsIterator();
1142
1143               if (mainIntersection == null || iterator.size() < mainIntersection.size()) {
1144                 while (iterator.hasNext()) {
1145                   final int id = iterator.next();
1146                   if (mainIntersection == null && (projectFilesFilter == null || projectFilesFilter.containsFileId(id)) ||
1147                       mainIntersection != null && mainIntersection.contains(id)
1148                     ) {
1149                     copy.add(id);
1150                   }
1151                 }
1152               }
1153               else {
1154                 mainIntersection.forEach(new TIntProcedure() {
1155                   final ValueContainer.IntPredicate predicate = valueIt.getValueAssociationPredicate();
1156
1157                   @Override
1158                   public boolean execute(int id) {
1159                     if (predicate.contains(id)) copy.add(id);
1160                     return true;
1161                   }
1162                 });
1163               }
1164             }
1165
1166             mainIntersection = copy;
1167             if (mainIntersection.isEmpty()) {
1168               return new TIntHashSet();
1169             }
1170           }
1171
1172           return mainIntersection;
1173         }
1174       };
1175
1176
1177     return processExceptions(indexId, null, filter, convertor);
1178   }
1179
1180   private static boolean processVirtualFiles(@NotNull TIntHashSet ids,
1181                                              @NotNull final GlobalSearchScope filter,
1182                                              @NotNull final Processor<VirtualFile> processor) {
1183     final PersistentFS fs = (PersistentFS)ManagingFS.getInstance();
1184     return ids.forEach(new TIntProcedure() {
1185       @Override
1186       public boolean execute(int id) {
1187         ProgressManager.checkCanceled();
1188         VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
1189         if (file != null && filter.accept(file)) {
1190           return processor.process(file);
1191         }
1192         return true;
1193       }
1194     });
1195   }
1196
1197   @Nullable
1198   public static Throwable getCauseToRebuildIndex(@NotNull RuntimeException e) {
1199     if (e instanceof IndexOutOfBoundsException) return e; // something wrong with direct byte buffer
1200     Throwable cause = e.getCause();
1201     if (cause instanceof StorageException || cause instanceof IOException ||
1202         cause instanceof IllegalArgumentException) return cause;
1203     return null;
1204   }
1205
1206   @Override
1207   public <K, V> boolean getFilesWithKey(@NotNull final ID<K, V> indexId,
1208                                         @NotNull final Set<K> dataKeys,
1209                                         @NotNull Processor<VirtualFile> processor,
1210                                         @NotNull GlobalSearchScope filter) {
1211     return processFilesContainingAllKeys(indexId, dataKeys, filter, null, processor);
1212   }
1213
1214   @Override
1215   public <K> void scheduleRebuild(@NotNull final ID<K, ?> indexId, @NotNull final Throwable e) {
1216     requestRebuild(indexId, new Throwable(e));
1217     try {
1218       checkRebuild(indexId, false);
1219     }
1220     catch (ProcessCanceledException ignored) {
1221     }
1222   }
1223
1224   private void checkRebuild(@NotNull final ID<?, ?> indexId, final boolean cleanupOnly) {
1225     final AtomicInteger status = ourRebuildStatus.get(indexId);
1226     if (status.get() == OK) {
1227       return;
1228     }
1229     if (status.compareAndSet(REQUIRES_REBUILD, REBUILD_IN_PROGRESS)) {
1230       cleanupProcessedFlag();
1231
1232       advanceIndexVersion(indexId);
1233
1234       final Runnable rebuildRunnable = new Runnable() {
1235         @Override
1236         public void run() {
1237           try {
1238             doClearIndex(indexId);
1239             if (!cleanupOnly) {
1240               scheduleIndexRebuild();
1241             }
1242           }
1243           catch (StorageException e) {
1244             requestRebuild(indexId);
1245             LOG.info(e);
1246           }
1247           finally {
1248             status.compareAndSet(REBUILD_IN_PROGRESS, OK);
1249           }
1250         }
1251       };
1252
1253       if (cleanupOnly || myIsUnitTestMode) {
1254         rebuildRunnable.run();
1255       }
1256       else {
1257         //noinspection SSBasedInspection
1258         ApplicationManager.getApplication().invokeLater(new Runnable() {
1259           @Override
1260           public void run() {
1261             new Task.Modal(null, "Updating index", false) {
1262               @Override
1263               public void run(@NotNull final ProgressIndicator indicator) {
1264                 indicator.setIndeterminate(true);
1265                 rebuildRunnable.run();
1266               }
1267             }.queue();
1268           }
1269         }, ModalityState.NON_MODAL);
1270       }
1271     }
1272
1273     if (status.get() == REBUILD_IN_PROGRESS) {
1274       throw new ProcessCanceledException();
1275     }
1276   }
1277
1278   private static void scheduleIndexRebuild() {
1279     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
1280       DumbService.getInstance(project).queueTask(new UnindexedFilesUpdater(project, false));
1281     }
1282   }
1283
1284   private void clearIndex(@NotNull final ID<?, ?> indexId) throws StorageException {
1285     advanceIndexVersion(indexId);
1286     doClearIndex(indexId);
1287   }
1288
1289   private void doClearIndex(ID<?, ?> indexId) throws StorageException {
1290     final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1291     assert index != null : "Index with key " + indexId + " not found or not registered properly";
1292     index.clear();
1293   }
1294
1295   private void advanceIndexVersion(ID<?, ?> indexId) {
1296     try {
1297       IndexingStamp.rewriteVersion(IndexInfrastructure.getVersionFile(indexId), myIndexIdToVersionMap.get(indexId));
1298     }
1299     catch (IOException e) {
1300       LOG.error(e);
1301     }
1302   }
1303
1304   @NotNull
1305   private Set<Document> getUnsavedDocuments() {
1306     Document[] documents = myFileDocumentManager.getUnsavedDocuments();
1307     if (documents.length == 0) return Collections.emptySet();
1308     if (documents.length == 1) return Collections.singleton(documents[0]);
1309     return new THashSet<Document>(Arrays.asList(documents));
1310   }
1311
1312   @NotNull
1313   private Set<Document> getTransactedDocuments() {
1314     return myTransactionMap.keySet();
1315   }
1316
1317   private void indexUnsavedDocuments(@NotNull ID<?, ?> indexId,
1318                                      @Nullable Project project,
1319                                      GlobalSearchScope filter,
1320                                      VirtualFile restrictedFile) throws StorageException {
1321     if (myUpToDateIndicesForUnsavedOrTransactedDocuments.contains(indexId)) {
1322       return; // no need to index unsaved docs
1323     }
1324
1325     Set<Document> documents = getUnsavedDocuments();
1326     boolean psiBasedIndex = myPsiDependentIndices.contains(indexId);
1327     if(psiBasedIndex) {
1328       Set<Document> transactedDocuments = getTransactedDocuments();
1329       if (documents.size() == 0) documents = transactedDocuments;
1330       else if (transactedDocuments.size() > 0) {
1331         documents = new THashSet<Document>(documents);
1332         documents.addAll(transactedDocuments);
1333       }
1334     }
1335
1336     if (!documents.isEmpty()) {
1337       // now index unsaved data
1338       final StorageGuard.StorageModeExitHandler guard = setDataBufferingEnabled(true);
1339       try {
1340         final Semaphore semaphore = myUnsavedDataIndexingSemaphores.get(indexId);
1341
1342         assert semaphore != null : "Semaphore for unsaved data indexing was not initialized for index " + indexId;
1343
1344         semaphore.down();
1345         boolean allDocsProcessed = true;
1346         try {
1347           for (Document document : documents) {
1348             allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter, restrictedFile);
1349             ProgressManager.checkCanceled();
1350           }
1351         }
1352         finally {
1353           semaphore.up();
1354
1355           while (!semaphore.waitFor(500)) { // may need to wait until another thread is done with indexing
1356             ProgressManager.checkCanceled();
1357             if (Thread.holdsLock(PsiLock.LOCK)) {
1358               break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
1359             }
1360           }
1361           if (allDocsProcessed && !hasActiveTransactions()) {
1362             ProgressManager.checkCanceled();
1363             // assume all tasks were finished or cancelled in the same time
1364             // safe to set the flag here, because it will be cleared under the WriteAction
1365             myUpToDateIndicesForUnsavedOrTransactedDocuments.add(indexId);
1366           }
1367         }
1368       }
1369       finally {
1370         guard.leave();
1371       }
1372     }
1373   }
1374
1375   private boolean hasActiveTransactions() {
1376     return !myTransactionMap.isEmpty();
1377   }
1378
1379   private interface DocumentContent {
1380     CharSequence getText();
1381
1382     long getModificationStamp();
1383   }
1384
1385   private static class AuthenticContent implements DocumentContent {
1386     private final Document myDocument;
1387
1388     private AuthenticContent(final Document document) {
1389       myDocument = document;
1390     }
1391
1392     @Override
1393     public CharSequence getText() {
1394       return myDocument.getImmutableCharSequence();
1395     }
1396
1397     @Override
1398     public long getModificationStamp() {
1399       return myDocument.getModificationStamp();
1400     }
1401   }
1402
1403   private static class PsiContent implements DocumentContent {
1404     private final Document myDocument;
1405     private final PsiFile myFile;
1406
1407     private PsiContent(final Document document, final PsiFile file) {
1408       myDocument = document;
1409       myFile = file;
1410     }
1411
1412     @Override
1413     public CharSequence getText() {
1414       if (myFile.getViewProvider().getModificationStamp() != myDocument.getModificationStamp()) {
1415         final ASTNode node = myFile.getNode();
1416         assert node != null;
1417         return node.getChars();
1418       }
1419       return myDocument.getImmutableCharSequence();
1420     }
1421
1422     @Override
1423     public long getModificationStamp() {
1424       return myFile.getViewProvider().getModificationStamp();
1425     }
1426   }
1427
1428   private static final Key<WeakReference<FileContentImpl>> ourFileContentKey = Key.create("unsaved.document.index.content");
1429
1430   // returns false if doc was not indexed because the file does not fit in scope
1431   private boolean indexUnsavedDocument(@NotNull final Document document, @NotNull final ID<?, ?> requestedIndexId, final Project project,
1432                                        @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedFile) {
1433     final VirtualFile vFile = myFileDocumentManager.getFile(document);
1434     if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) {
1435       return true;
1436     }
1437
1438     if (restrictedFile != null) {
1439       if (!Comparing.equal(vFile, restrictedFile)) {
1440         return false;
1441       }
1442     }
1443     else if (filter != null && !filter.accept(vFile)) {
1444       return false;
1445     }
1446
1447     final PsiFile dominantContentFile = project == null ? null : findLatestKnownPsiForUncomittedDocument(document, project);
1448
1449     final DocumentContent content;
1450     if (dominantContentFile != null && dominantContentFile.getViewProvider().getModificationStamp() != document.getModificationStamp()) {
1451       content = new PsiContent(document, dominantContentFile);
1452     }
1453     else {
1454       content = new AuthenticContent(document);
1455     }
1456
1457     boolean psiBasedIndex = myPsiDependentIndices.contains(requestedIndexId);
1458
1459     final long currentDocStamp = psiBasedIndex ? PsiDocumentManager.getInstance(project).getLastCommittedStamp(document) : content.getModificationStamp();
1460     final long previousDocStamp = myLastIndexedDocStamps.getAndSet(document, requestedIndexId, currentDocStamp);
1461     if (currentDocStamp != previousDocStamp) {
1462       final CharSequence contentText = content.getText();
1463       FileTypeManagerImpl.cacheFileType(vFile, vFile.getFileType());
1464       try {
1465         if (!isTooLarge(vFile, contentText.length()) &&
1466             getAffectedIndexCandidates(vFile).contains(requestedIndexId) &&
1467             getInputFilter(requestedIndexId).acceptInput(vFile)) {
1468           // Reasonably attempt to use same file content when calculating indices as we can evaluate them several at once and store in file content
1469           WeakReference<FileContentImpl> previousContentRef = document.getUserData(ourFileContentKey);
1470           FileContentImpl previousContent = com.intellij.reference.SoftReference.dereference(previousContentRef);
1471           final FileContentImpl newFc;
1472           if (previousContent != null && previousContent.getStamp() == currentDocStamp) {
1473             newFc = previousContent;
1474           }
1475           else {
1476             newFc = new FileContentImpl(vFile, contentText, vFile.getCharset(), currentDocStamp);
1477             document.putUserData(ourFileContentKey, new WeakReference<FileContentImpl>(newFc));
1478           }
1479
1480           initFileContent(newFc, project, dominantContentFile);
1481
1482           if (content instanceof AuthenticContent) {
1483             newFc.putUserData(PlatformIdTableBuilding.EDITOR_HIGHLIGHTER, EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document));
1484           }
1485
1486           final int inputId = Math.abs(getFileId(vFile));
1487           try {
1488             getIndex(requestedIndexId).update(inputId, newFc).compute();
1489           } catch (ProcessCanceledException pce) {
1490             myLastIndexedDocStamps.getAndSet(document, requestedIndexId, previousDocStamp);
1491             throw pce;
1492           }
1493           finally {
1494             cleanFileContent(newFc, dominantContentFile);
1495           }
1496         }
1497       }
1498       finally {
1499         FileTypeManagerImpl.cacheFileType(vFile, null);
1500       }
1501     }
1502     return true;
1503   }
1504
1505   private final TaskQueue myContentlessIndicesUpdateQueue = new TaskQueue(10000);
1506
1507   private final StorageGuard myStorageLock = new StorageGuard();
1508   private volatile boolean myPreviousDataBufferingState;
1509   private final Object myBufferingStateUpdateLock = new Object();
1510
1511   @NotNull
1512   private StorageGuard.StorageModeExitHandler setDataBufferingEnabled(final boolean enabled) {
1513     StorageGuard.StorageModeExitHandler storageModeExitHandler = myStorageLock.enter(enabled);
1514
1515     if (myPreviousDataBufferingState != enabled) {
1516       synchronized (myBufferingStateUpdateLock) {
1517         if (myPreviousDataBufferingState != enabled) {
1518           for (ID<?, ?> indexId : myIndices.keySet()) {
1519             final MapReduceIndex index = (MapReduceIndex)getIndex(indexId);
1520             assert index != null;
1521             ((MemoryIndexStorage)index.getStorage()).setBufferingEnabled(enabled);
1522           }
1523           myPreviousDataBufferingState = enabled;
1524         }
1525       }
1526     }
1527     return storageModeExitHandler;
1528   }
1529
1530   private void cleanupMemoryStorage() {
1531     myLastIndexedDocStamps.clear();
1532     for (ID<?, ?> indexId : myIndices.keySet()) {
1533       final MapReduceIndex index = (MapReduceIndex)getIndex(indexId);
1534       assert index != null;
1535       final MemoryIndexStorage memStorage = (MemoryIndexStorage)index.getStorage();
1536       index.getWriteLock().lock();
1537       try {
1538         memStorage.clearMemoryMap();
1539       }
1540       finally {
1541         index.getWriteLock().unlock();
1542       }
1543       memStorage.fireMemoryStorageCleared();
1544     }
1545   }
1546
1547   private void dropUnregisteredIndices() {
1548     final Set<String> indicesToDrop = readRegisteredIndexNames();
1549     for (ID<?, ?> key : myIndices.keySet()) {
1550       indicesToDrop.remove(key.toString());
1551     }
1552     for (String s : indicesToDrop) {
1553       FileUtil.deleteWithRenaming(IndexInfrastructure.getIndexRootDir(ID.create(s)));
1554     }
1555   }
1556
1557   @Override
1558   public void requestRebuild(ID<?, ?> indexId, Throwable throwable) {
1559     cleanupProcessedFlag();
1560     boolean requiresRebuildWasSet = ourRebuildStatus.get(indexId).compareAndSet(OK, REQUIRES_REBUILD);
1561     if (requiresRebuildWasSet) LOG.info("Rebuild requested for index " + indexId, throwable);
1562   }
1563
1564   private <K, V> UpdatableIndex<K, V, FileContent> getIndex(ID<K, V> indexId) {
1565     final Pair<UpdatableIndex<?, ?, FileContent>, InputFilter> pair = myIndices.get(indexId);
1566
1567     assert pair != null : "Index data is absent for index " + indexId;
1568
1569     //noinspection unchecked
1570     return (UpdatableIndex<K, V, FileContent>)pair.getFirst();
1571   }
1572
1573   private InputFilter getInputFilter(@NotNull ID<?, ?> indexId) {
1574     final Pair<UpdatableIndex<?, ?, FileContent>, InputFilter> pair = myIndices.get(indexId);
1575
1576     assert pair != null : "Index data is absent for index " + indexId;
1577
1578     return pair.getSecond();
1579   }
1580
1581   public int getNumberOfPendingInvalidations() {
1582     return myChangedFilesCollector.getNumberOfPendingInvalidations();
1583   }
1584
1585   public int getChangedFileCount() {
1586     return myChangedFilesCollector.getAllFilesToUpdate().size();
1587   }
1588
1589   @NotNull
1590   public Collection<VirtualFile> getFilesToUpdate(final Project project) {
1591     return ContainerUtil.findAll(myChangedFilesCollector.getAllFilesToUpdate(), new Condition<VirtualFile>() {
1592       @Override
1593       public boolean value(VirtualFile virtualFile) {
1594         for (IndexableFileSet set : myIndexableSets) {
1595           final Project proj = myIndexableSetToProjectMap.get(set);
1596           if (proj != null && !proj.equals(project)) {
1597             continue; // skip this set as associated with a different project
1598           }
1599           if (set.isInSet(virtualFile)) {
1600             return true;
1601           }
1602         }
1603         return false;
1604       }
1605     });
1606   }
1607
1608   public boolean isFileUpToDate(VirtualFile file) {
1609     return !myChangedFilesCollector.myFilesToUpdate.contains(file);
1610   }
1611
1612   void processRefreshedFile(@NotNull Project project, @NotNull final com.intellij.ide.caches.FileContent fileContent) {
1613     myChangedFilesCollector.tryToEnsureAllInvalidateTasksCompleted();
1614     myChangedFilesCollector.processFileImpl(project, fileContent); // ProcessCanceledException will cause re-adding the file to processing list
1615   }
1616
1617   public void indexFileContent(@Nullable Project project, @NotNull com.intellij.ide.caches.FileContent content) {
1618     VirtualFile file = content.getVirtualFile();
1619     // if file was scheduled for update due to vfs events then it is present in myFilesToUpdate
1620     // in this case we consider that current indexing (out of roots backed CacheUpdater) will cover its content
1621     // todo this assumption isn't correct for vfs events happened between content loading and indexing itself
1622     // proper fix will when events handling will be out of direct execution by EDT
1623     myChangedFilesCollector.myFilesToUpdate.remove(file);
1624     doIndexFileContent(project, content);
1625   }
1626
1627   private void doIndexFileContent(@Nullable Project project, @NotNull com.intellij.ide.caches.FileContent content) {
1628     myChangedFilesCollector.tryToEnsureAllInvalidateTasksCompleted();
1629     final VirtualFile file = content.getVirtualFile();
1630
1631     FileType fileType = file.getFileType();
1632     FileTypeManagerImpl.cacheFileType(file, fileType);
1633
1634     try {
1635       PsiFile psiFile = null;
1636       FileContentImpl fc = null;
1637
1638       final List<ID<?, ?>> affectedIndexCandidates = getAffectedIndexCandidates(file);
1639       //noinspection ForLoopReplaceableByForEach
1640       for (int i = 0, size = affectedIndexCandidates.size(); i < size; ++i) {
1641         final ID<?, ?> indexId = affectedIndexCandidates.get(i);
1642         if (shouldIndexFile(file, indexId)) {
1643           if (fc == null) {
1644             if (project == null) {
1645               project = ProjectUtil.guessProjectForFile(file);
1646             }
1647
1648             byte[] currentBytes;
1649             try {
1650               currentBytes = content.getBytes();
1651             }
1652             catch (IOException e) {
1653               currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY;
1654             }
1655             fc = new FileContentImpl(file, currentBytes);
1656
1657             if (!fileType.isBinary() && IdIndex.ourSnapshotMappingsEnabled) {
1658               try {
1659                 byte[] hash = ContentHashesSupport.calcContentHashWithFileType(
1660                   currentBytes,
1661                   fc.getCharset(),
1662                   SubstitutedFileType.substituteFileType(file, fileType, project)
1663                 );
1664                 fc.setHash(hash);
1665               } catch (IOException e) {
1666                 LOG.error(e);
1667               }
1668             }
1669
1670             psiFile = content.getUserData(IndexingDataKeys.PSI_FILE);
1671             initFileContent(fc, project, psiFile);
1672           }
1673
1674           try {
1675             ProgressManager.checkCanceled();
1676             updateSingleIndex(indexId, file, fc);
1677           }
1678           catch (ProcessCanceledException e) {
1679             cleanFileContent(fc, psiFile);
1680             myChangedFilesCollector.scheduleForUpdate(file);
1681             throw e;
1682           }
1683           catch (StorageException e) {
1684             requestRebuild(indexId);
1685             LOG.info(e);
1686           }
1687         }
1688       }
1689
1690       if (psiFile != null) {
1691         psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1692       }
1693     }
1694     finally {
1695       FileTypeManagerImpl.cacheFileType(file, null);
1696     }
1697   }
1698
1699   public boolean isIndexingCandidate(@NotNull VirtualFile file, @NotNull ID<?, ?> indexId) {
1700     return !isTooLarge(file) && getAffectedIndexCandidates(file).contains(indexId);
1701   }
1702
1703   @NotNull
1704   private List<ID<?, ?>> getAffectedIndexCandidates(@NotNull VirtualFile file) {
1705     if (file.isDirectory()) {
1706       return isProjectOrWorkspaceFile(file, null) ?  Collections.<ID<?,?>>emptyList() : myIndicesForDirectories;
1707     }
1708     FileType fileType = file.getFileType();
1709     if(isProjectOrWorkspaceFile(file, fileType)) return Collections.emptyList();
1710     List<ID<?, ?>> ids = myFileType2IndicesWithFileTypeInfoMap.get(fileType);
1711     if (ids == null) ids = myIndicesWithoutFileTypeInfo;
1712     return ids;
1713   }
1714
1715   private static void cleanFileContent(@NotNull FileContentImpl fc, PsiFile psiFile) {
1716     if (psiFile != null) psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1717     fc.putUserData(IndexingDataKeys.PSI_FILE, null);
1718   }
1719
1720   private static void initFileContent(@NotNull FileContentImpl fc, Project project, PsiFile psiFile) {
1721     if (psiFile != null) {
1722       psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1723       fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
1724     }
1725
1726     fc.putUserData(IndexingDataKeys.PROJECT, project);
1727   }
1728
1729   private void updateSingleIndex(@NotNull ID<?, ?> indexId, @NotNull final VirtualFile file, @Nullable FileContent currentFC)
1730     throws StorageException {
1731     if (ourRebuildStatus.get(indexId).get() == REQUIRES_REBUILD) {
1732       return; // the index is scheduled for rebuild, no need to update
1733     }
1734     myLocalModCount++;
1735
1736     final int inputId = Math.abs(getFileId(file));
1737     final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1738     assert index != null;
1739
1740     if (currentFC != null && currentFC.getUserData(ourPhysicalContentKey) == null) {
1741       currentFC.putUserData(ourPhysicalContentKey, Boolean.TRUE);
1742     }
1743     // important: no hard referencing currentFC to avoid OOME, the methods introduced for this purpose!
1744     final Computable<Boolean> update = index.update(inputId, currentFC);
1745
1746     scheduleUpdate(indexId,
1747                    createUpdateComputableWithBufferingDisabled(update),
1748                    createIndexedStampUpdateRunnable(indexId, file, currentFC != null)
1749     );
1750   }
1751
1752   static final Key<Boolean> ourPhysicalContentKey = Key.create("physical.content.flag");
1753
1754   @NotNull
1755   private Runnable createIndexedStampUpdateRunnable(@NotNull final ID<?, ?> indexId,
1756                                                     @NotNull final VirtualFile file,
1757                                                     final boolean hasContent) {
1758     return new Runnable() {
1759       @Override
1760       public void run() {
1761         if (file.isValid()) {
1762           int fileId = getIdMaskingNonIdBasedFile(file);
1763           if (hasContent) {
1764             IndexingStamp.setFileIndexedStateCurrent(fileId, indexId);
1765           }
1766           else {
1767             IndexingStamp.setFileIndexedStateUnindexed(fileId, indexId);
1768           }
1769           if (myNotRequiringContentIndices.contains(indexId)) IndexingStamp.flushCache(fileId);
1770         }
1771       }
1772     };
1773   }
1774
1775   @NotNull
1776   private Computable<Boolean> createUpdateComputableWithBufferingDisabled(@NotNull final Computable<Boolean> update) {
1777     return new Computable<Boolean>() {
1778       @Override
1779       public Boolean compute() {
1780         Boolean result;
1781         final StorageGuard.StorageModeExitHandler lock = setDataBufferingEnabled(false);
1782         try {
1783           result = update.compute();
1784         }
1785         finally {
1786           lock.leave();
1787         }
1788         return result;
1789       }
1790     };
1791   }
1792
1793   private void scheduleUpdate(@NotNull ID<?, ?> indexId, @NotNull Computable<Boolean> update, @NotNull Runnable successRunnable) {
1794     if (myNotRequiringContentIndices.contains(indexId)) {
1795       myContentlessIndicesUpdateQueue.submit(update, successRunnable);
1796     }
1797     else {
1798       Boolean result = update.compute();
1799       if (result == Boolean.TRUE) ApplicationManager.getApplication().runReadAction(successRunnable);
1800     }
1801   }
1802
1803   private boolean needsFileContentLoading(@NotNull ID<?, ?> indexId) {
1804     return !myNotRequiringContentIndices.contains(indexId);
1805   }
1806
1807   private @Nullable IndexableFileSet getIndexableSetForFile(VirtualFile file) {
1808     for (IndexableFileSet set : myIndexableSets) {
1809       if (set.isInSet(file)) {
1810         return set;
1811       }
1812     }
1813     return null;
1814   }
1815
1816
1817   private abstract static class InvalidationTask implements Runnable {
1818     private final VirtualFile mySubj;
1819
1820     protected InvalidationTask(@NotNull VirtualFile subj) {
1821       mySubj = subj;
1822     }
1823
1824     @NotNull
1825     public VirtualFile getSubj() {
1826       return mySubj;
1827     }
1828   }
1829
1830   private static class SilentProgressIndicator extends DelegatingProgressIndicator {
1831     // suppress verbose messages
1832
1833     private SilentProgressIndicator(ProgressIndicator indicator) {
1834       super(indicator);
1835     }
1836
1837     @Nullable
1838     private static SilentProgressIndicator create() {
1839       final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
1840       return indicator != null ? new SilentProgressIndicator(indicator) : null;
1841     }
1842
1843     @Override
1844     public void setText(String text) {
1845     }
1846
1847     @Override
1848     public String getText() {
1849       return "";
1850     }
1851
1852     @Override
1853     public void setText2(String text) {
1854     }
1855
1856     @Override
1857     public String getText2() {
1858       return "";
1859     }
1860   }
1861
1862   private final class ChangedFilesCollector extends VirtualFileAdapter implements BulkFileListener {
1863     private final Set<VirtualFile> myFilesToUpdate = ContainerUtil.newConcurrentSet();
1864     private final Queue<InvalidationTask> myFutureInvalidations = new ConcurrentLinkedQueue<InvalidationTask>();
1865
1866     private final ManagingFS myManagingFS = ManagingFS.getInstance();
1867
1868     @Override
1869     public void fileMoved(@NotNull VirtualFileMoveEvent event) {
1870       markDirty(event, false);
1871     }
1872
1873     @Override
1874     public void fileCreated(@NotNull final VirtualFileEvent event) {
1875       markDirty(event, false);
1876     }
1877
1878     @Override
1879     public void fileCopied(@NotNull final VirtualFileCopyEvent event) {
1880       markDirty(event, false);
1881     }
1882
1883     @Override
1884     public void beforeFileDeletion(@NotNull final VirtualFileEvent event) {
1885       invalidateIndices(event.getFile(), false);
1886     }
1887
1888     @Override
1889     public void beforeContentsChange(@NotNull final VirtualFileEvent event) {
1890       invalidateIndices(event.getFile(), true);
1891     }
1892
1893     @Override
1894     public void contentsChanged(@NotNull final VirtualFileEvent event) {
1895       markDirty(event, true);
1896     }
1897
1898     @Override
1899     public void beforePropertyChange(@NotNull final VirtualFilePropertyEvent event) {
1900       String propertyName = event.getPropertyName();
1901
1902       if (propertyName.equals(VirtualFile.PROP_NAME)) {
1903         // indexes may depend on file name
1904         // name change may lead to filetype change so the file might become not indexable
1905         // in general case have to 'unindex' the file and index it again if needed after the name has been changed
1906         invalidateIndices(event.getFile(), false);
1907       } else if (propertyName.equals(VirtualFile.PROP_ENCODING)) {
1908         invalidateIndices(event.getFile(), true);
1909       }
1910     }
1911
1912     @Override
1913     public void propertyChanged(@NotNull final VirtualFilePropertyEvent event) {
1914       String propertyName = event.getPropertyName();
1915       if (propertyName.equals(VirtualFile.PROP_NAME)) {
1916         // indexes may depend on file name
1917         markDirty(event, false);
1918       } else if (propertyName.equals(VirtualFile.PROP_ENCODING)) {
1919         markDirty(event, true);
1920       }
1921     }
1922
1923     private void markDirty(@NotNull final VirtualFileEvent event, final boolean contentChange) {
1924       final VirtualFile eventFile = event.getFile();
1925       cleanProcessedFlag(eventFile);
1926       if (!contentChange) {
1927         myUpdatingFiles.incrementAndGet();
1928       }
1929
1930       iterateIndexableFiles(eventFile, new Processor<VirtualFile>() {
1931         @Override
1932         public boolean process(@NotNull final VirtualFile file) {
1933           // handle 'content-less' indices separately
1934           boolean fileIsDirectory = file.isDirectory();
1935           if (!contentChange) {
1936             FileContent fileContent = null;
1937             for (ID<?, ?> indexId : fileIsDirectory ? myIndicesForDirectories : myNotRequiringContentIndices) {
1938               if (getInputFilter(indexId).acceptInput(file)) {
1939                 try {
1940                   if (fileContent == null) {
1941                     fileContent = new FileContentImpl(file);
1942                   }
1943                   updateSingleIndex(indexId, file, fileContent);
1944                 }
1945                 catch (StorageException e) {
1946                   LOG.info(e);
1947                   requestRebuild(indexId);
1948                 }
1949               }
1950             }
1951           }
1952           // For 'normal indices' schedule the file for update and reset stamps for all affected indices (there
1953           // can be client that used indices between before and after events, in such case indices are up to date due to force update
1954           // with old content)
1955           if (!fileIsDirectory) {
1956             if (isTooLarge(file)) {
1957               // large file might be scheduled for update in before event when its size was not large
1958               myChangedFilesCollector.myFilesToUpdate.remove(file);
1959             } else {
1960               FileTypeManagerImpl.cacheFileType(file, file.getFileType());
1961               try {
1962                 final List<ID<?, ?>> candidates = getAffectedIndexCandidates(file);
1963                 int fileId = getIdMaskingNonIdBasedFile(file);
1964                 //noinspection ForLoopReplaceableByForEach
1965                 boolean scheduleForUpdate = false;
1966                 boolean resetStamp = false;
1967
1968                 //noinspection ForLoopReplaceableByForEach
1969                 for (int i = 0, size = candidates.size(); i < size; ++i) {
1970                   final ID<?, ?> indexId = candidates.get(i);
1971                   if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) {
1972                     if (IndexingStamp.isFileIndexedStateCurrent(fileId, indexId)) {
1973                       IndexingStamp.setFileIndexedStateOutdated(fileId, indexId);
1974                       resetStamp = true;
1975                     }
1976                     scheduleForUpdate = true;
1977                   }
1978                 }
1979
1980                 if (scheduleForUpdate) {
1981                   if (resetStamp) IndexingStamp.flushCache(file);
1982                   scheduleForUpdate(file);
1983                 }
1984
1985                 if (!myUpToDateIndicesForUnsavedOrTransactedDocuments.isEmpty()) {
1986                   clearUpToDateStateForPsiIndicesOfUnsavedDocuments(file);
1987                 }
1988               }
1989               finally {
1990                 FileTypeManagerImpl.cacheFileType(file, null);
1991               }
1992             }
1993           }
1994
1995           return true;
1996         }
1997       });
1998       IndexingStamp.flushCaches();
1999       if (!contentChange) {
2000         if (myUpdatingFiles.decrementAndGet() == 0) {
2001           ++myFilesModCount;
2002         }
2003       }
2004     }
2005
2006     private void scheduleForUpdate(VirtualFile file) {
2007       myFilesToUpdate.add(file);
2008     }
2009
2010     private void invalidateIndices(@NotNull final VirtualFile file, final boolean markForReindex) {
2011       VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
2012         @Override
2013         public boolean visitFile(@NotNull VirtualFile file) {
2014           if (isUnderConfigOrSystem(file)) {
2015             return false;
2016           }
2017           if (file.isDirectory()) {
2018             invalidateIndicesForFile(file, markForReindex);
2019             if (!isMock(file) && !myManagingFS.wereChildrenAccessed(file)) {
2020               return false;
2021             }
2022           }
2023           else {
2024             invalidateIndicesForFile(file, markForReindex);
2025           }
2026           return true;
2027         }
2028
2029         @Override
2030         public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
2031           return file instanceof NewVirtualFile ? ((NewVirtualFile)file).iterInDbChildren() : null;
2032         }
2033       });
2034     }
2035
2036     private void invalidateIndicesForFile(@NotNull final VirtualFile file, boolean markForReindex) {
2037       cleanProcessedFlag(file);
2038       IndexingStamp.flushCache(file);
2039
2040       final int fileId = getIdMaskingNonIdBasedFile(file);
2041       List<ID<?, ?>> nontrivialFileIndexedStates = IndexingStamp.getNontrivialFileIndexedStates(fileId);
2042
2043       if (!markForReindex) {  // markForReindex really means content changed
2044         for (ID<?, ?> indexId : nontrivialFileIndexedStates) {
2045           if (myNotRequiringContentIndices.contains(indexId)) {
2046             try {
2047               updateSingleIndex(indexId, file, null);
2048             }
2049             catch (StorageException e) {
2050               LOG.info(e);
2051               requestRebuild(indexId);
2052             }
2053           }
2054         }
2055         myFilesToUpdate.remove(file); // no need to update it anymore
2056       }
2057
2058       Collection<ID<?, ?>> fileIndexedStatesToUpdate = ContainerUtil.intersection(nontrivialFileIndexedStates, myRequiringContentIndices);
2059
2060       if (markForReindex) {
2061         // only mark the file as outdated, reindex will be done lazily
2062         if (!fileIndexedStatesToUpdate.isEmpty()) {
2063
2064           //noinspection ForLoopReplaceableByForEach
2065           for (int i = 0, size = nontrivialFileIndexedStates.size(); i < size; ++i) {
2066             final ID<?, ?> indexId = nontrivialFileIndexedStates.get(i);
2067             if (needsFileContentLoading(indexId) && IndexingStamp.isFileIndexedStateCurrent(fileId, indexId)) {
2068               IndexingStamp.setFileIndexedStateOutdated(fileId, indexId);
2069             }
2070           }
2071
2072           clearUpToDateStateForPsiIndicesOfUnsavedDocuments(file);
2073
2074           // the file is for sure not a dir and it was previously indexed by at least one index AND it belongs to some update set
2075           if (!isTooLarge(file) && getIndexableSetForFile(file) != null) scheduleForUpdate(file);
2076         }
2077       }
2078       else if (!fileIndexedStatesToUpdate.isEmpty()) { // file was removed, its data should be (lazily) wiped for every index
2079         final Collection<ID<?, ?>> finalFileIndexedStatesToUpdate = fileIndexedStatesToUpdate;
2080         myFutureInvalidations.offer(new InvalidationTask(file) {
2081           @Override
2082           public void run() {
2083             removeFileDataFromIndices(finalFileIndexedStatesToUpdate, getSubj());
2084           }
2085         });
2086       }
2087
2088       IndexingStamp.flushCache(file);
2089     }
2090
2091     private void removeFileDataFromIndices(@NotNull Collection<ID<?, ?>> affectedIndices, @NotNull VirtualFile file) {
2092       Throwable unexpectedError = null;
2093       for (ID<?, ?> indexId : affectedIndices) {
2094         try {
2095           updateSingleIndex(indexId, file, null);
2096         }
2097         catch (StorageException e) {
2098           LOG.info(e);
2099           requestRebuild(indexId);
2100         }
2101         catch (ProcessCanceledException pce) {
2102           LOG.error(pce);
2103         }
2104         catch (Throwable e) {
2105           LOG.info(e);
2106           if (unexpectedError == null) {
2107             unexpectedError = e;
2108           }
2109         }
2110       }
2111       IndexingStamp.flushCache(file);
2112       if (unexpectedError != null) {
2113         LOG.error(unexpectedError);
2114       }
2115     }
2116
2117     public int getNumberOfPendingInvalidations() {
2118       return myFutureInvalidations.size();
2119     }
2120
2121     public void ensureAllInvalidateTasksCompleted() {
2122       ensureAllInvalidateTasksCompleted(false);
2123     }
2124
2125     public void tryToEnsureAllInvalidateTasksCompleted() {
2126       ensureAllInvalidateTasksCompleted(true);
2127     }
2128
2129     private void ensureAllInvalidateTasksCompleted(boolean doCheckCancelledBetweenInvalidations) {
2130       final int size = getNumberOfPendingInvalidations();
2131       if (size == 0) {
2132         return;
2133       }
2134
2135       if (doCheckCancelledBetweenInvalidations) {
2136         while (true) {
2137           InvalidationTask task = myFutureInvalidations.poll();
2138
2139           if (task == null) {
2140             break;
2141           }
2142
2143           ProgressManager.getInstance().executeNonCancelableSection(task);
2144           ProgressManager.checkCanceled();
2145         }
2146       }
2147       else {
2148         ProgressManager.getInstance().executeNonCancelableSection(
2149           new Runnable() {
2150             @Override
2151             public void run() {
2152               final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
2153               indicator.setText("");
2154               int count = 0;
2155               while (true) {
2156                 InvalidationTask task = myFutureInvalidations.poll();
2157
2158                 if (task == null) {
2159                   break;
2160                 }
2161                 indicator.setFraction((double)count++ / size);
2162                 task.run();
2163               }
2164             }
2165           }
2166         );
2167       }
2168     }
2169
2170     private void iterateIndexableFiles(@NotNull final VirtualFile file, @NotNull final Processor<VirtualFile> processor) {
2171       if (file.isDirectory()) {
2172         final ContentIterator iterator = new ContentIterator() {
2173           @Override
2174           public boolean processFile(@NotNull final VirtualFile fileOrDir) {
2175             processor.process(fileOrDir);
2176             return true;
2177           }
2178         };
2179
2180         for (IndexableFileSet set : myIndexableSets) {
2181           if (set.isInSet(file)) {
2182             set.iterateIndexableFilesIn(file, iterator);
2183           }
2184         }
2185       }
2186       else {
2187         if (getIndexableSetForFile(file) != null) processor.process(file);
2188       }
2189     }
2190
2191     public Collection<VirtualFile> getAllFilesToUpdate() {
2192       if (myFilesToUpdate.isEmpty()) {
2193         return Collections.emptyList();
2194       }
2195       return new ArrayList<VirtualFile>(myFilesToUpdate);
2196     }
2197
2198     private final AtomicReference<UpdateSemaphore> myUpdateSemaphoreRef = new AtomicReference<UpdateSemaphore>(null);
2199
2200     @NotNull
2201     private UpdateSemaphore obtainForceUpdateSemaphore() {
2202       UpdateSemaphore newValue = null;
2203       while (true) {
2204         final UpdateSemaphore currentValue = myUpdateSemaphoreRef.get();
2205         if (currentValue != null) {
2206           return currentValue;
2207         }
2208         if (newValue == null) { // lazy init
2209           newValue = new UpdateSemaphore();
2210         }
2211         if (myUpdateSemaphoreRef.compareAndSet(null, newValue)) {
2212           return newValue;
2213         }
2214       }
2215     }
2216
2217     private void releaseForceUpdateSemaphore(UpdateSemaphore semaphore) {
2218       myUpdateSemaphoreRef.compareAndSet(semaphore, null);
2219     }
2220
2221     private void forceUpdate(@Nullable Project project, @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedTo) {
2222       myChangedFilesCollector.tryToEnsureAllInvalidateTasksCompleted();
2223       ProjectIndexableFilesFilter indexableFilesFilter = projectIndexableFiles(project);
2224
2225       UpdateSemaphore updateSemaphore;
2226       do {
2227         updateSemaphore = obtainForceUpdateSemaphore();
2228         try {
2229           for (VirtualFile file : getAllFilesToUpdate()) {
2230             if (indexableFilesFilter != null && file instanceof VirtualFileWithId && !indexableFilesFilter.containsFileId(
2231               ((VirtualFileWithId)file).getId())) {
2232               continue;
2233             }
2234
2235             if (filter == null || filter.accept(file) || Comparing.equal(file, restrictedTo)) {
2236               try {
2237                 updateSemaphore.down();
2238                 // process only files that can affect result
2239                 processFileImpl(project, new com.intellij.ide.caches.FileContent(file));
2240               }
2241               catch (ProcessCanceledException e) {
2242                 updateSemaphore.reportUpdateCanceled();
2243                 throw e;
2244               }
2245               finally {
2246                 updateSemaphore.up();
2247               }
2248             }
2249           }
2250
2251           // If several threads entered the method at the same time and there were files to update,
2252           // all the threads should leave the method synchronously after all the files scheduled for update are reindexed,
2253           // no matter which thread will do reindexing job.
2254           // Thus we ensure that all the threads that entered the method will get the most recent data
2255
2256           while (!updateSemaphore.waitFor(500)) { // may need to wait until another thread is done with indexing
2257             if (Thread.holdsLock(PsiLock.LOCK)) {
2258               break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
2259             }
2260           }
2261         }
2262         finally {
2263           releaseForceUpdateSemaphore(updateSemaphore);
2264         }
2265         // if some other thread was unable to complete indexing because of PCE,
2266         // we should try again and ensure the file is indexed before proceeding further
2267       }
2268       while (updateSemaphore.isUpdateCanceled());
2269     }
2270
2271     private void processFileImpl(Project project, @NotNull final com.intellij.ide.caches.FileContent fileContent) {
2272       final VirtualFile file = fileContent.getVirtualFile();
2273       final boolean reallyRemoved = myFilesToUpdate.remove(file);
2274       if (reallyRemoved && file.isValid()) {
2275         int fileId = getIdMaskingNonIdBasedFile(file);
2276         try {
2277           if (isTooLarge(file)) {
2278             List<ID<?, ?>> nontrivialFileIndexedStates = IndexingStamp.getNontrivialFileIndexedStates(fileId);
2279             removeFileDataFromIndices(ContainerUtil.intersection(nontrivialFileIndexedStates, myRequiringContentIndices), file);
2280           }
2281           else {
2282             try {
2283               doIndexFileContent(project, fileContent);
2284             } catch (ProcessCanceledException ex) {
2285               myFilesToUpdate.add(file); // PCE from running invalidation tasks should reschedule file processing
2286               throw ex;
2287             }
2288           }
2289         }
2290         finally {
2291           IndexingStamp.flushCache(file);
2292         }
2293       }
2294     }
2295
2296     @Override
2297     public void before(@NotNull List<? extends VFileEvent> events) {
2298       myContentlessIndicesUpdateQueue.signalUpdateStart();
2299       myContentlessIndicesUpdateQueue.ensureUpToDate();
2300
2301       for (VFileEvent event : events) {
2302         Object requestor = event.getRequestor();
2303         if (requestor instanceof FileDocumentManager ||
2304             requestor instanceof PsiManager ||
2305             requestor == LocalHistory.VFS_EVENT_REQUESTOR) {
2306           cleanupMemoryStorage();
2307           break;
2308         }
2309       }
2310       for (VFileEvent event : events) {
2311         BulkVirtualFileListenerAdapter.fireBefore(this, event);
2312       }
2313     }
2314
2315     @Override
2316     public void after(@NotNull List<? extends VFileEvent> events) {
2317       myContentlessIndicesUpdateQueue.ensureUpToDate();
2318
2319       for (VFileEvent event : events) {
2320         BulkVirtualFileListenerAdapter.fireAfter(this, event);
2321       }
2322       myContentlessIndicesUpdateQueue.signalUpdateEnd();
2323     }
2324   }
2325
2326   private boolean clearUpToDateStateForPsiIndicesOfUnsavedDocuments(@NotNull VirtualFile file) {
2327     Document document = myFileDocumentManager.getCachedDocument(file);
2328
2329     if (document != null && myFileDocumentManager.isDocumentUnsaved(document)) {
2330       if (!myUpToDateIndicesForUnsavedOrTransactedDocuments.isEmpty()) {
2331         for (ID<?, ?> psiBackedIndex : myPsiDependentIndices) {
2332           myUpToDateIndicesForUnsavedOrTransactedDocuments.remove(psiBackedIndex);
2333         }
2334       }
2335
2336       myLastIndexedDocStamps.clearForDocument(document); // Q: non psi indices
2337       document.putUserData(ourFileContentKey, null);
2338
2339       return true;
2340     }
2341     return false;
2342   }
2343
2344   private static int getIdMaskingNonIdBasedFile(VirtualFile file) {
2345     return file instanceof VirtualFileWithId ?((VirtualFileWithId)file).getId() : IndexingStamp.INVALID_FILE_ID;
2346   }
2347
2348   private class UnindexedFilesFinder implements CollectingContentIterator {
2349     private final List<VirtualFile> myFiles = new ArrayList<VirtualFile>();
2350     @Nullable
2351     private final ProgressIndicator myProgressIndicator;
2352
2353     private UnindexedFilesFinder(@Nullable ProgressIndicator indicator) {
2354       myProgressIndicator = indicator;
2355     }
2356
2357     @NotNull
2358     @Override
2359     public List<VirtualFile> getFiles() {
2360       synchronized (myFiles) {
2361         return myFiles;
2362       }
2363     }
2364
2365     @Override
2366     public boolean processFile(@NotNull final VirtualFile file) {
2367       if (!file.isValid()) {
2368         return true;
2369       }
2370       if (file instanceof VirtualFileSystemEntry && ((VirtualFileSystemEntry)file).isFileIndexed()) {
2371         return true;
2372       }
2373
2374       if (!(file instanceof VirtualFileWithId)) {
2375         return true;
2376       }
2377       try {
2378         FileType type = file.getFileType();
2379         FileTypeManagerImpl.cacheFileType(file, type);
2380
2381         boolean oldStuff = true;
2382         if (file.isDirectory() || !isTooLarge(file)) {
2383           final List<ID<?, ?>> affectedIndexCandidates = getAffectedIndexCandidates(file);
2384           //noinspection ForLoopReplaceableByForEach
2385           for (int i = 0, size = affectedIndexCandidates.size(); i < size; ++i) {
2386             final ID<?, ?> indexId = affectedIndexCandidates.get(i);
2387             try {
2388               if (needsFileContentLoading(indexId) && shouldIndexFile(file, indexId)) {
2389                 synchronized (myFiles) {
2390                   myFiles.add(file);
2391                 }
2392                 oldStuff = false;
2393                 break;
2394               }
2395             }
2396             catch (RuntimeException e) {
2397               final Throwable cause = e.getCause();
2398               if (cause instanceof IOException || cause instanceof StorageException) {
2399                 LOG.info(e);
2400                 requestRebuild(indexId);
2401               }
2402               else {
2403                 throw e;
2404               }
2405             }
2406           }
2407         }
2408         FileContent fileContent = null;
2409         for (ID<?, ?> indexId : myNotRequiringContentIndices) {
2410           if (shouldIndexFile(file, indexId)) {
2411             oldStuff = false;
2412             try {
2413               if (fileContent == null) {
2414                 fileContent = new FileContentImpl(file);
2415               }
2416               updateSingleIndex(indexId, file, fileContent);
2417             }
2418             catch (StorageException e) {
2419               LOG.info(e);
2420               requestRebuild(indexId);
2421             }
2422           }
2423         }
2424         IndexingStamp.flushCache(file);
2425
2426         if (oldStuff && file instanceof VirtualFileSystemEntry) {
2427           ((VirtualFileSystemEntry)file).setFileIndexed(true);
2428         }
2429       }
2430       finally {
2431         FileTypeManagerImpl.cacheFileType(file, null);
2432       }
2433
2434       if (myProgressIndicator != null && file.isDirectory()) { // once for dir is cheap enough
2435         myProgressIndicator.checkCanceled();
2436         myProgressIndicator.setText("Scanning files to index");
2437       }
2438       return true;
2439     }
2440   }
2441
2442   private boolean shouldIndexFile(@NotNull VirtualFile file, @NotNull ID<?, ?> indexId) {
2443     return getInputFilter(indexId).acceptInput(file) &&
2444            (isMock(file) || !IndexingStamp.isFileIndexedStateCurrent(((NewVirtualFile)file).getId(), indexId));
2445   }
2446
2447   private boolean isUnderConfigOrSystem(@NotNull VirtualFile file) {
2448     final String filePath = file.getPath();
2449     return myConfigPath != null && FileUtil.startsWith(filePath, myConfigPath) ||
2450            myLogPath != null && FileUtil.startsWith(filePath, myLogPath);
2451   }
2452
2453   private static boolean isMock(final VirtualFile file) {
2454     return !(file instanceof NewVirtualFile);
2455   }
2456
2457   private boolean isTooLarge(@NotNull VirtualFile file) {
2458     if (SingleRootFileViewProvider.isTooLargeForIntelligence(file)) {
2459       return !myNoLimitCheckTypes.contains(file.getFileType());
2460     }
2461     return false;
2462   }
2463
2464   private boolean isTooLarge(@NotNull VirtualFile file, long contentSize) {
2465     if (SingleRootFileViewProvider.isTooLargeForIntelligence(file, contentSize)) {
2466       return !myNoLimitCheckTypes.contains(file.getFileType());
2467     }
2468     return false;
2469   }
2470
2471   @NotNull
2472   public CollectingContentIterator createContentIterator(@Nullable ProgressIndicator indicator) {
2473     return new UnindexedFilesFinder(indicator);
2474   }
2475
2476   @Override
2477   public void registerIndexableSet(@NotNull IndexableFileSet set, @Nullable Project project) {
2478     myIndexableSets.add(set);
2479     myIndexableSetToProjectMap.put(set, project);
2480     if (project != null) {
2481       ((PsiManagerImpl)PsiManager.getInstance(project)).addTreeChangePreprocessor(new PsiTreeChangePreprocessor() {
2482         @Override
2483         public void treeChanged(@NotNull PsiTreeChangeEventImpl event) {
2484           if (event.isGenericChange() &&
2485               event.getCode() == PsiTreeChangeEventImpl.PsiEventType.CHILDREN_CHANGED) {
2486             PsiFile file = event.getFile();
2487             if (file != null) {
2488               VirtualFile virtualFile = file.getVirtualFile();
2489               if (!clearUpToDateStateForPsiIndicesOfUnsavedDocuments(virtualFile)) {
2490                 // change in persistent file
2491                 if (virtualFile instanceof VirtualFileWithId) {
2492                   int fileId = ((VirtualFileWithId)virtualFile).getId();
2493                   boolean wasIndexed = false;
2494                   for (ID<?, ?> psiBackedIndex : myPsiDependentIndices) {
2495                     if (IndexingStamp.isFileIndexedStateCurrent(fileId, psiBackedIndex)) {
2496                       IndexingStamp.setFileIndexedStateOutdated(fileId, psiBackedIndex);
2497                       wasIndexed = true;
2498                     }
2499                   }
2500                   if (wasIndexed) {
2501                     myChangedFilesCollector.scheduleForUpdate(virtualFile);
2502                     IndexingStamp.flushCache(fileId);
2503                   }
2504                 }
2505               }
2506             }
2507           }
2508         }
2509       });
2510     }
2511   }
2512
2513   @Override
2514   public void removeIndexableSet(@NotNull IndexableFileSet set) {
2515     if (!myIndexableSetToProjectMap.containsKey(set)) return;
2516     myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
2517     IndexingStamp.flushCaches();
2518     myIndexableSets.remove(set);
2519     myIndexableSetToProjectMap.remove(set);
2520
2521     for (VirtualFile file : myChangedFilesCollector.getAllFilesToUpdate()) {
2522       if (getIndexableSetForFile(file) == null) {
2523         myChangedFilesCollector.myFilesToUpdate.remove(file);
2524       }
2525     }
2526   }
2527
2528   @Override
2529   public VirtualFile findFileById(Project project, int id) {
2530     return IndexInfrastructure.findFileById((PersistentFS)ManagingFS.getInstance(), id);
2531   }
2532
2533   @Nullable
2534   private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) {
2535     return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc);
2536   }
2537
2538   private static class IndexableFilesFilter implements InputFilter {
2539     private final InputFilter myDelegate;
2540
2541     private IndexableFilesFilter(InputFilter delegate) {
2542       myDelegate = delegate;
2543     }
2544
2545     @Override
2546     public boolean acceptInput(@NotNull final VirtualFile file) {
2547       return file instanceof VirtualFileWithId && myDelegate.acceptInput(file);
2548     }
2549   }
2550
2551   private static void cleanupProcessedFlag() {
2552     final VirtualFile[] roots = ManagingFS.getInstance().getRoots();
2553     for (VirtualFile root : roots) {
2554       cleanProcessedFlag(root);
2555     }
2556   }
2557
2558   private static void cleanProcessedFlag(@NotNull final VirtualFile file) {
2559     if (!(file instanceof VirtualFileSystemEntry)) return;
2560
2561     final VirtualFileSystemEntry nvf = (VirtualFileSystemEntry)file;
2562     if (file.isDirectory()) {
2563       nvf.setFileIndexed(false);
2564       for (VirtualFile child : nvf.getCachedChildren()) {
2565         cleanProcessedFlag(child);
2566       }
2567     }
2568     else {
2569       nvf.setFileIndexed(false);
2570     }
2571   }
2572
2573   @Override
2574   public void iterateIndexableFiles(@NotNull final ContentIterator processor, @NotNull final Project project, final ProgressIndicator indicator) {
2575     if (project.isDisposed()) {
2576       return;
2577     }
2578
2579     List<Runnable> tasks = new ArrayList<Runnable>();
2580
2581     final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
2582     tasks.add(new Runnable() {
2583       @Override
2584       public void run() {
2585         projectFileIndex.iterateContent(processor);
2586       }
2587     });
2588     /*
2589     Module[] modules = ModuleManager.getInstance(project).getModules();
2590     for(final Module module: modules) {
2591       tasks.add(new Runnable() {
2592         @Override
2593         public void run() {
2594           if (module.isDisposed()) return;
2595           ModuleRootManager.getInstance(module).getFileIndex().iterateContent(processor);
2596         }
2597       });
2598     }*/
2599
2600     final Set<VirtualFile> visitedRoots = new THashSet<VirtualFile>();
2601     for (IndexedRootsProvider provider : Extensions.getExtensions(IndexedRootsProvider.EP_NAME)) {
2602       //important not to depend on project here, to support per-project background reindex
2603       // each client gives a project to FileBasedIndex
2604       if (project.isDisposed()) {
2605         return;
2606       }
2607       for (final VirtualFile root : IndexableSetContributor.getRootsToIndex(provider)) {
2608         if (visitedRoots.add(root)) {
2609           tasks.add(new Runnable() {
2610             @Override
2611             public void run() {
2612               if (project.isDisposed() || !root.isValid()) return;
2613               iterateRecursively(root, processor, indicator, visitedRoots, null);
2614             }
2615           });
2616         }
2617       }
2618       for (final VirtualFile root : IndexableSetContributor.getProjectRootsToIndex(provider, project)) {
2619         if (visitedRoots.add(root)) {
2620           tasks.add(new Runnable() {
2621             @Override
2622             public void run() {
2623               if (project.isDisposed() || !root.isValid()) return;
2624               iterateRecursively(root, processor, indicator, visitedRoots, null);
2625             }
2626           });
2627         }
2628       }
2629     }
2630
2631     // iterate associated libraries
2632     for (final Module module : ModuleManager.getInstance(project).getModules()) {
2633       OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries();
2634       for (OrderEntry orderEntry : orderEntries) {
2635         if (orderEntry instanceof LibraryOrSdkOrderEntry) {
2636           if (orderEntry.isValid()) {
2637             final LibraryOrSdkOrderEntry entry = (LibraryOrSdkOrderEntry)orderEntry;
2638             final VirtualFile[] libSources = entry.getRootFiles(OrderRootType.SOURCES);
2639             final VirtualFile[] libClasses = entry.getRootFiles(OrderRootType.CLASSES);
2640             for (VirtualFile[] roots : new VirtualFile[][]{libSources, libClasses}) {
2641               for (final VirtualFile root : roots) {
2642                 if (visitedRoots.add(root)) {
2643                   tasks.add(new Runnable() {
2644                     @Override
2645                     public void run() {
2646                       if (project.isDisposed() || module.isDisposed() || !root.isValid()) return;
2647                       iterateRecursively(root, processor, indicator, null, projectFileIndex);
2648                     }
2649                   });
2650                 }
2651               }
2652             }
2653           }
2654         }
2655       }
2656     }
2657
2658     if (Registry.is("idea.concurrent.scanning.files.to.index")) {
2659       JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tasks, indicator, true, false, new Processor<Runnable>() {
2660         @Override
2661         public boolean process(Runnable runnable) {
2662           runnable.run();
2663           return true;
2664         }
2665       });
2666     } else {
2667       for(Runnable r:tasks) r.run();
2668     }
2669   }
2670
2671   private static void iterateRecursively(@Nullable final VirtualFile root,
2672                                          @NotNull final ContentIterator processor,
2673                                          @Nullable final ProgressIndicator indicator,
2674                                          @Nullable final Set<VirtualFile> visitedRoots,
2675                                          @Nullable final ProjectFileIndex projectFileIndex) {
2676     if (root == null) {
2677       return;
2678     }
2679
2680     VfsUtilCore.visitChildrenRecursively(root, new VirtualFileVisitor() {
2681       @Override
2682       public boolean visitFile(@NotNull VirtualFile file) {
2683         if (visitedRoots != null && !root.equals(file) && file.isDirectory() && !visitedRoots.add(file)) {
2684           return false; // avoid visiting files more than once, e.g. additional indexed roots intersect sometimes
2685         }
2686         if (projectFileIndex != null && projectFileIndex.isExcluded(file)) {
2687           return false;
2688         }
2689         if (indicator != null) indicator.checkCanceled();
2690
2691         processor.processFile(file);
2692         return true;
2693       }
2694     });
2695   }
2696
2697   @SuppressWarnings({"WhileLoopSpinsOnField", "SynchronizeOnThis"})
2698   private static class StorageGuard {
2699     private int myHolds = 0;
2700     private int myWaiters = 0;
2701
2702     public interface StorageModeExitHandler {
2703       void leave();
2704     }
2705
2706     private final StorageModeExitHandler myTrueStorageModeExitHandler = new StorageModeExitHandler() {
2707       @Override
2708       public void leave() {
2709         StorageGuard.this.leave(true);
2710       }
2711     };
2712     private final StorageModeExitHandler myFalseStorageModeExitHandler = new StorageModeExitHandler() {
2713       @Override
2714       public void leave() {
2715         StorageGuard.this.leave(false);
2716       }
2717     };
2718
2719     @NotNull
2720     private synchronized StorageModeExitHandler enter(boolean mode) {
2721       if (mode) {
2722         while (myHolds < 0) {
2723           doWait();
2724         }
2725         myHolds++;
2726         return myTrueStorageModeExitHandler;
2727       }
2728       else {
2729         while (myHolds > 0) {
2730           doWait();
2731         }
2732         myHolds--;
2733         return myFalseStorageModeExitHandler;
2734       }
2735     }
2736
2737     private void doWait() {
2738       try {
2739         ++myWaiters;
2740         wait();
2741       }
2742       catch (InterruptedException ignored) {
2743       } finally {
2744         --myWaiters;
2745       }
2746     }
2747
2748     private synchronized void leave(boolean mode) {
2749       myHolds += mode ? -1 : 1;
2750       if (myHolds == 0 && myWaiters > 0) {
2751         notifyAll();
2752       }
2753     }
2754   }
2755 }