test killability
[idea/community.git] / platform / lang-impl / src / com / intellij / util / indexing / FileBasedIndex.java
1 /*
2  * Copyright 2000-2009 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.history.LocalHistory;
21 import com.intellij.ide.caches.CacheUpdater;
22 import com.intellij.lang.ASTNode;
23 import com.intellij.notification.NotificationDisplayType;
24 import com.intellij.notification.NotificationGroup;
25 import com.intellij.notification.NotificationType;
26 import com.intellij.openapi.application.ApplicationAdapter;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.application.PathManager;
29 import com.intellij.openapi.components.ApplicationComponent;
30 import com.intellij.openapi.diagnostic.Logger;
31 import com.intellij.openapi.editor.Document;
32 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
33 import com.intellij.openapi.editor.impl.EditorHighlighterCache;
34 import com.intellij.openapi.extensions.Extensions;
35 import com.intellij.openapi.fileEditor.FileDocumentManager;
36 import com.intellij.openapi.fileEditor.FileDocumentManagerAdapter;
37 import com.intellij.openapi.fileTypes.*;
38 import com.intellij.openapi.fileTypes.impl.FileTypeManagerImpl;
39 import com.intellij.openapi.module.Module;
40 import com.intellij.openapi.module.ModuleManager;
41 import com.intellij.openapi.progress.*;
42 import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
43 import com.intellij.openapi.project.*;
44 import com.intellij.openapi.roots.*;
45 import com.intellij.openapi.util.*;
46 import com.intellij.openapi.util.io.FileUtil;
47 import com.intellij.openapi.vfs.*;
48 import com.intellij.openapi.vfs.ex.VirtualFileManagerEx;
49 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
50 import com.intellij.openapi.vfs.newvfs.ManagingFS;
51 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
52 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
53 import com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon;
54 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
55 import com.intellij.psi.*;
56 import com.intellij.psi.impl.PsiDocumentTransactionListener;
57 import com.intellij.psi.impl.source.PsiFileImpl;
58 import com.intellij.psi.search.EverythingGlobalScope;
59 import com.intellij.psi.search.GlobalSearchScope;
60 import com.intellij.psi.stubs.SerializationManager;
61 import com.intellij.util.ArrayUtil;
62 import com.intellij.util.CommonProcessors;
63 import com.intellij.util.Processor;
64 import com.intellij.util.SmartList;
65 import com.intellij.util.concurrency.Semaphore;
66 import com.intellij.util.containers.ConcurrentHashSet;
67 import com.intellij.util.containers.ContainerUtil;
68 import com.intellij.util.containers.HashMap;
69 import com.intellij.util.containers.HashSet;
70 import com.intellij.util.io.*;
71 import com.intellij.util.io.storage.HeavyProcessLatch;
72 import com.intellij.util.messages.MessageBus;
73 import com.intellij.util.messages.MessageBusConnection;
74 import gnu.trove.TIntHashSet;
75 import gnu.trove.TIntIterator;
76 import gnu.trove.TObjectIntHashMap;
77 import org.jetbrains.annotations.NonNls;
78 import org.jetbrains.annotations.NotNull;
79 import org.jetbrains.annotations.Nullable;
80
81 import javax.swing.*;
82 import java.io.*;
83 import java.util.*;
84 import java.util.concurrent.ConcurrentLinkedQueue;
85 import java.util.concurrent.ScheduledFuture;
86 import java.util.concurrent.atomic.AtomicBoolean;
87 import java.util.concurrent.atomic.AtomicInteger;
88
89 /**
90  * @author Eugene Zhuravlev
91  *         Date: Dec 20, 2007
92  */
93
94 public class FileBasedIndex implements ApplicationComponent {
95   private static final Logger LOG = Logger.getInstance("#com.intellij.util.indexing.FileBasedIndex");
96   @NonNls
97   private static final String CORRUPTION_MARKER_NAME = "corruption.marker";
98   private final Map<ID<?, ?>, Pair<UpdatableIndex<?, ?, FileContent>, InputFilter>> myIndices = new HashMap<ID<?, ?>, Pair<UpdatableIndex<?, ?, FileContent>, InputFilter>>();
99   private final Map<ID<?, ?>, Semaphore> myUnsavedDataIndexingSemaphores = new HashMap<ID<?,?>, Semaphore>();
100   private final TObjectIntHashMap<ID<?, ?>> myIndexIdToVersionMap = new TObjectIntHashMap<ID<?, ?>>();
101   private final Set<ID<?, ?>> myNotRequiringContentIndices = new HashSet<ID<?, ?>>();
102   private final Set<ID<?, ?>> myRequiringContentIndices = new HashSet<ID<?, ?>>();
103   private final Set<FileType> myNoLimitCheckTypes = new HashSet<FileType>();
104
105   private final PerIndexDocumentVersionMap myLastIndexedDocStamps = new PerIndexDocumentVersionMap();
106   private final ChangedFilesCollector myChangedFilesCollector;
107
108   private final List<IndexableFileSet> myIndexableSets = ContainerUtil.createEmptyCOWList();
109   private final Map<IndexableFileSet, Project> myIndexableSetToProjectMap = new HashMap<IndexableFileSet, Project>();
110
111   private static final int OK = 1;
112   private static final int REQUIRES_REBUILD = 2;
113   private static final int REBUILD_IN_PROGRESS = 3;
114   private static final Map<ID<?, ?>, AtomicInteger> ourRebuildStatus = new HashMap<ID<?,?>, AtomicInteger>();
115
116   private final VirtualFileManagerEx myVfManager;
117   private final FileDocumentManager myFileDocumentManager;
118   private final FileTypeManager myFileTypeManager;
119   private final ConcurrentHashSet<ID<?, ?>> myUpToDateIndices = new ConcurrentHashSet<ID<?, ?>>();
120   private final Map<Document, PsiFile> myTransactionMap = new HashMap<Document, PsiFile>();
121
122   private static final int ALREADY_PROCESSED = 0x02;
123   @Nullable private final String myConfigPath;
124   @Nullable private final String mySystemPath;
125   private final boolean myIsUnitTestMode;
126   private ScheduledFuture<?> myFlushingFuture;
127   private volatile int myLocalModCount;
128
129   public void requestReindex(final VirtualFile file) {
130     myChangedFilesCollector.invalidateIndices(file, true);
131   }
132
133   public void requestReindexExcluded(final VirtualFile file) {
134     myChangedFilesCollector.invalidateIndices(file, false);
135   }
136
137   public interface InputFilter {
138     boolean acceptInput(VirtualFile file);
139   }
140
141   public FileBasedIndex(final VirtualFileManagerEx vfManager, FileDocumentManager fdm,
142                         FileTypeManager fileTypeManager, MessageBus bus, SerializationManager sm /*need this parameter to ensure component dependency*/) throws IOException {
143     myVfManager = vfManager;
144     myFileDocumentManager = fdm;
145     myFileTypeManager = fileTypeManager;
146     myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
147     myConfigPath = calcConfigPath(PathManager.getConfigPath());
148     mySystemPath = calcConfigPath(PathManager.getSystemPath());
149
150     final MessageBusConnection connection = bus.connect();
151     connection.subscribe(PsiDocumentTransactionListener.TOPIC, new PsiDocumentTransactionListener() {
152       @Override
153       public void transactionStarted(final Document doc, final PsiFile file) {
154         if (file != null) {
155           synchronized (myTransactionMap) {
156             myTransactionMap.put(doc, file);
157           }
158           myUpToDateIndices.clear();
159         }
160       }
161
162       @Override
163       public void transactionCompleted(final Document doc, final PsiFile file) {
164         synchronized (myTransactionMap) {
165           myTransactionMap.remove(doc);
166         }
167       }
168     });
169
170     connection.subscribe(FileTypeManager.TOPIC, new FileTypeListener() {
171       private Map<FileType, Set<String>> myTypeToExtensionMap;
172       @Override
173       public void beforeFileTypesChanged(final FileTypeEvent event) {
174         cleanupProcessedFlag();
175         myTypeToExtensionMap = new HashMap<FileType, Set<String>>();
176         for (FileType type : myFileTypeManager.getRegisteredFileTypes()) {
177           myTypeToExtensionMap.put(type, getExtensions(type));
178         }
179       }
180
181       @Override
182       public void fileTypesChanged(final FileTypeEvent event) {
183         final Map<FileType, Set<String>> oldExtensions = myTypeToExtensionMap;
184         myTypeToExtensionMap = null;
185         if (oldExtensions != null) {
186           final Map<FileType, Set<String>> newExtensions = new HashMap<FileType, Set<String>>();
187           for (FileType type : myFileTypeManager.getRegisteredFileTypes()) {
188             newExtensions.put(type, getExtensions(type));
189           }
190           // we are interested only in extension changes or removals.
191           // addition of an extension is handled separately by RootsChanged event
192           if (!newExtensions.keySet().containsAll(oldExtensions.keySet())) {
193             rebuildAllIndices();
194             return;
195           }
196           for (Map.Entry<FileType, Set<String>> entry : oldExtensions.entrySet()) {
197             FileType fileType = entry.getKey();
198             Set<String> strings = entry.getValue();
199             if (!newExtensions.get(fileType).containsAll(strings)) {
200               rebuildAllIndices();
201               return;
202             }
203           }
204         }
205       }
206
207       private Set<String> getExtensions(FileType type) {
208         final Set<String> set = new HashSet<String>();
209         for (FileNameMatcher matcher : myFileTypeManager.getAssociations(type)) {
210           set.add(matcher.getPresentableString());
211         }
212         return set;
213       }
214
215       private void rebuildAllIndices() {
216         for (ID<?, ?> indexId : myIndices.keySet()) {
217           try {
218             clearIndex(indexId);
219           }
220           catch (StorageException e) {
221             LOG.info(e);
222           }
223         }
224         scheduleIndexRebuild(true);
225       }
226     });
227
228     connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
229       @Override
230       public void before(List<? extends VFileEvent> events) {
231         for (VFileEvent event : events) {
232           final Object requestor = event.getRequestor();
233           if (requestor instanceof FileDocumentManager || requestor instanceof PsiManager || requestor == LocalHistory.VFS_EVENT_REQUESTOR) {
234             cleanupMemoryStorage();
235             break;
236           }
237         }
238       }
239
240       @Override
241       public void after(List<? extends VFileEvent> events) {
242       }
243     });
244
245     connection.subscribe(AppTopics.FILE_DOCUMENT_SYNC, new FileDocumentManagerAdapter() {
246       @Override
247       public void fileContentReloaded(VirtualFile file, Document document) {
248         cleanupMemoryStorage();
249       }
250
251       @Override
252       public void unsavedDocumentsDropped() {
253         cleanupMemoryStorage();
254       }
255     });
256
257     ApplicationManager.getApplication().addApplicationListener(new ApplicationAdapter() {
258       @Override
259       public void writeActionStarted(Object action) {
260         myUpToDateIndices.clear();
261       }
262     });
263
264     myChangedFilesCollector = new ChangedFilesCollector();
265
266     /*
267     final File workInProgressFile = getMarkerFile();
268     if (workInProgressFile.exists()) {
269       // previous IDEA session was closed incorrectly, so drop all indices
270       FileUtil.delete(PathManager.getIndexRoot());
271     }
272     */
273
274     try {
275       final FileBasedIndexExtension[] extensions = Extensions.getExtensions(FileBasedIndexExtension.EXTENSION_POINT_NAME);
276       for (FileBasedIndexExtension<?, ?> extension : extensions) {
277         ourRebuildStatus.put(extension.getName(), new AtomicInteger(OK));
278       }
279
280       final File corruptionMarker = new File(PathManager.getIndexRoot(), CORRUPTION_MARKER_NAME);
281       final boolean currentVersionCorrupted = corruptionMarker.exists();
282       boolean versionChanged = false;
283       for (FileBasedIndexExtension<?, ?> extension : extensions) {
284         versionChanged |= registerIndexer(extension, currentVersionCorrupted);
285       }
286       FileUtil.delete(corruptionMarker);
287       String rebuildNotification = null;
288       if (currentVersionCorrupted) {
289         rebuildNotification = "Index files on disk are corrupted. Indices will be rebuilt.";
290       }
291       else if (versionChanged) {
292         rebuildNotification = "Index file format has changed for some indices. These indices will be rebuilt.";
293       }
294       if (rebuildNotification != null && !ApplicationManager.getApplication().isHeadlessEnvironment()) {
295         new NotificationGroup("Indexing", NotificationDisplayType.BALLOON, false)
296           .createNotification("Index Rebuild", rebuildNotification, NotificationType.INFORMATION, null).notify(null);
297       }
298       dropUnregisteredIndices();
299
300       // check if rebuild was requested for any index during registration
301       for (ID<?, ?> indexId : myIndices.keySet()) {
302         if (ourRebuildStatus.get(indexId).compareAndSet(REQUIRES_REBUILD, OK)) {
303           try {
304             clearIndex(indexId);
305           }
306           catch (StorageException e) {
307             requestRebuild(indexId);
308             LOG.error(e);
309           }
310         }
311       }
312
313       myVfManager.addVirtualFileListener(myChangedFilesCollector);
314
315       registerIndexableSet(new AdditionalIndexableFileSet(), null);
316     }
317     finally {
318       ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
319         @Override
320         public void run() {
321           ShutDownTracker.invokeAndWait(ApplicationManager.getApplication().isUnitTestMode(), new Runnable() {
322             @Override
323             public void run() {
324               performShutdown();
325             }
326           });
327         }
328       });
329       //FileUtil.createIfDoesntExist(workInProgressFile);
330       saveRegisteredIndices(myIndices.keySet());
331       myFlushingFuture = FlushingDaemon.everyFiveSeconds(new Runnable() {
332         int lastModCount = 0;
333         @Override
334         public void run() {
335           if (lastModCount == myLocalModCount) {
336             flushAllIndices(lastModCount);
337           }
338           lastModCount = myLocalModCount;
339         }
340       });
341
342     }
343   }
344
345   @Override
346   public void initComponent() {
347   }
348
349   private static String calcConfigPath(final String path) {
350     try {
351       final String _path = FileUtil.toSystemIndependentName(new File(path).getCanonicalPath());
352       return _path.endsWith("/")? _path : _path + "/" ;
353     }
354     catch (IOException e) {
355       LOG.info(e);
356       return null;
357     }
358   }
359
360   private static class FileBasedIndexHolder {
361     private static final FileBasedIndex ourInstance = ApplicationManager.getApplication().getComponent(FileBasedIndex.class);
362   }
363
364   public static FileBasedIndex getInstance() {
365     return FileBasedIndexHolder.ourInstance;
366   }
367
368   /**
369    * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted @param extension
370    * @param isCurrentVersionCorrupted
371    */
372   private <K, V> boolean registerIndexer(final FileBasedIndexExtension<K, V> extension, final boolean isCurrentVersionCorrupted) throws IOException {
373     final ID<K, V> name = extension.getName();
374     final int version = extension.getVersion();
375     final File versionFile = IndexInfrastructure.getVersionFile(name);
376     final boolean versionFileExisted = versionFile.exists();
377     boolean versionChanged = false;
378     if (isCurrentVersionCorrupted || IndexInfrastructure.versionDiffers(versionFile, version)) {
379       if (!isCurrentVersionCorrupted && versionFileExisted) {
380         versionChanged = true;
381         LOG.info("Version has changed for index " + extension.getName() + ". The index will be rebuilt.");
382       }
383       FileUtil.delete(IndexInfrastructure.getIndexRootDir(name));
384       IndexInfrastructure.rewriteVersion(versionFile, version);
385     }
386
387     for (int attempt = 0; attempt < 2; attempt++) {
388       try {
389         final MapIndexStorage<K, V> storage = new MapIndexStorage<K, V>(IndexInfrastructure.getStorageFile(name), extension.getKeyDescriptor(), extension.getValueExternalizer(), extension.getCacheSize());
390         final MemoryIndexStorage<K, V> memStorage = new MemoryIndexStorage<K, V>(storage);
391         final UpdatableIndex<K, V, FileContent> index = createIndex(name, extension, memStorage);
392         final InputFilter inputFilter = extension.getInputFilter();
393         
394         assert inputFilter != null : "Index extension " + name + " must provide non-null input filter";
395         
396         myIndices.put(name, new Pair<UpdatableIndex<?,?, FileContent>, InputFilter>(index, new IndexableFilesFilter(inputFilter)));
397         myUnsavedDataIndexingSemaphores.put(name, new Semaphore());
398         myIndexIdToVersionMap.put(name, version);
399         if (!extension.dependsOnFileContent()) {
400           myNotRequiringContentIndices.add(name);
401         }
402         else {
403           myRequiringContentIndices.add(name);
404         }
405         myNoLimitCheckTypes.addAll(extension.getFileTypesWithSizeLimitNotApplicable());
406         break;
407       }
408       catch (IOException e) {
409         LOG.info(e);
410         FileUtil.delete(IndexInfrastructure.getIndexRootDir(name));
411         IndexInfrastructure.rewriteVersion(versionFile, version);
412       }
413     }
414     return versionChanged;
415   }
416
417   private static void saveRegisteredIndices(Collection<ID<?, ?>> ids) {
418     final File file = getRegisteredIndicesFile();
419     try {
420       FileUtil.createIfDoesntExist(file);
421       final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
422       try {
423         os.writeInt(ids.size());
424         for (ID<?, ?> id : ids) {
425           IOUtil.writeString(id.toString(), os);
426         }
427       }
428       finally {
429         os.close();
430       }
431     }
432     catch (IOException ignored) {
433     }
434   }
435
436   private static Set<String> readRegistsredIndexNames() {
437     final Set<String> result = new HashSet<String>();
438     try {
439       final DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile())));
440       try {
441         final int size = in.readInt();
442         for (int idx = 0; idx < size; idx++) {
443           result.add(IOUtil.readString(in));
444         }
445       }
446       finally {
447         in.close();
448       }
449     }
450     catch (IOException ignored) {
451     }
452     return result;
453   }
454
455   private static File getRegisteredIndicesFile() {
456     return new File(PathManager.getIndexRoot(), "registered");
457   }
458
459   private <K, V> UpdatableIndex<K, V, FileContent> createIndex(final ID<K, V> indexId, final FileBasedIndexExtension<K, V> extension, final MemoryIndexStorage<K, V> storage) throws IOException {
460     final MapReduceIndex<K, V, FileContent> index;
461     if (extension instanceof CustomImplementationFileBasedIndexExtension) {
462       final UpdatableIndex<K, V, FileContent> custom = ((CustomImplementationFileBasedIndexExtension<K, V, FileContent>)extension).createIndexImplementation(indexId, this, storage);
463       
464       assert custom != null : "Custom index implementation must not be null; index: " + indexId;
465       
466       if (!(custom instanceof MapReduceIndex)) {
467         return custom;
468       }
469       index = (MapReduceIndex<K,V, FileContent>)custom;
470     }
471     else {
472       index = new MapReduceIndex<K, V, FileContent>(indexId, extension.getIndexer(), storage);
473     }
474
475     final KeyDescriptor<K> keyDescriptor = extension.getKeyDescriptor();
476     index.setInputIdToDataKeysIndex(new Factory<PersistentHashMap<Integer, Collection<K>>>() {
477       @Override
478       public PersistentHashMap<Integer, Collection<K>> create() {
479         try {
480           return createIdToDataKeysIndex(indexId, keyDescriptor, storage);
481         }
482         catch (IOException e) {
483           throw new RuntimeException(e);
484         }
485       }
486     });
487
488     return index;
489   }
490
491   private static <K> PersistentHashMap<Integer, Collection<K>> createIdToDataKeysIndex(final ID<K, ?> indexId,
492                                                                                        final KeyDescriptor<K> keyDescriptor,
493                                                                                        MemoryIndexStorage<K, ?> storage) throws IOException {
494     final File indexStorageFile = IndexInfrastructure.getInputIndexStorageFile(indexId);
495     final Ref<Boolean> isBufferingMode = new Ref<Boolean>(false);
496     final Map<Integer, Collection<K>> tempMap = new HashMap<Integer, Collection<K>>();
497
498     final DataExternalizer<Collection<K>> dataExternalizer = new DataExternalizer<Collection<K>>() {
499       @Override
500       public void save(DataOutput out, Collection<K> value) throws IOException {
501         try {
502           DataInputOutputUtil.writeINT(out, value.size());
503           for (K key : value) {
504             keyDescriptor.save(out, key);
505           }
506         }
507         catch (IllegalArgumentException e) {
508           throw new IOException("Error saving data for index " + indexId, e);
509         }
510       }
511
512       @Override
513       public Collection<K> read(DataInput in) throws IOException {
514         try {
515           final int size = DataInputOutputUtil.readINT(in);
516           final List<K> list = new ArrayList<K>(size);
517           for (int idx = 0; idx < size; idx++) {
518             list.add(keyDescriptor.read(in));
519           }
520           return list;
521         }
522         catch (IllegalArgumentException e) {
523           throw new IOException("Error reading data for index " + indexId, e);
524         }
525       }
526     };
527     
528     // Important! Update IdToDataKeysIndex depending on the sate of "buffering" flag from the MemoryStorage.
529     // If buffering is on, all changes should be done in memory (similar to the way it is done in memory storage).
530     // 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
531     // wrong sets of keys for the given file. This will lead to unpretictable results in main index because it will not be
532     // cleared properly before updating (removed data will still be present on disk). See IDEA-52223 for illustration of possible effects.
533
534     final PersistentHashMap<Integer, Collection<K>> map = new PersistentHashMap<Integer, Collection<K>>(
535       indexStorageFile, new EnumeratorIntegerDescriptor(), dataExternalizer
536     ) {
537
538       @Override
539       protected Collection<K> doGet(Integer integer) throws IOException {
540         if (isBufferingMode.get()) {
541           final Collection<K> collection = tempMap.get(integer);
542           if (collection != null) {
543             return collection;
544           }
545         }
546         return super.doGet(integer);
547       }
548
549       @Override
550       protected void doPut(Integer integer, Collection<K> ks) throws IOException {
551         if (isBufferingMode.get()) {
552           tempMap.put(integer, ks == null? Collections.<K>emptySet() : ks);
553         }
554         else {
555           super.doPut(integer, ks);
556         }
557       }
558
559       @Override
560       protected void doRemove(Integer integer) throws IOException {
561         if (isBufferingMode.get()) {
562           tempMap.put(integer, Collections.<K>emptySet());
563         }
564         else {
565           super.doRemove(integer);
566         }
567       }
568     };
569
570     storage.addBufferingStateListsner(new MemoryIndexStorage.BufferingStateListener() {
571       @Override
572       public void bufferingStateChanged(boolean newState) {
573         synchronized (map) {
574           isBufferingMode.set(newState);
575         }
576       }
577       @Override
578       public void memoryStorageCleared() {
579         synchronized (map) {
580           tempMap.clear();
581         }
582       }
583     });
584     return map;
585   }
586
587   @Override
588   @NonNls
589   @NotNull
590   public String getComponentName() {
591     return "FileBasedIndex";
592   }
593
594   @Override
595   public void disposeComponent() {
596     performShutdown();
597   }
598
599   private final AtomicBoolean myShutdownPerformed = new AtomicBoolean(false);
600
601   private void performShutdown() {
602     if (!myShutdownPerformed.compareAndSet(false, true)) {
603       return; // already shut down
604     }
605     try {
606       if (myFlushingFuture != null) {
607         myFlushingFuture.cancel(false);
608         myFlushingFuture = null;
609       }
610
611       myFileDocumentManager.saveAllDocuments();
612     }
613     finally {
614       LOG.info("START INDEX SHUTDOWN");
615       try {
616         myChangedFilesCollector.forceUpdate(null, null, null, true);
617
618         for (ID<?, ?> indexId : myIndices.keySet()) {
619           final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
620           assert index != null;
621           checkRebuild(indexId, true); // if the index was scheduled for rebuild, only clean it
622           //LOG.info("DISPOSING " + indexId);
623           index.dispose();
624         }
625
626         myVfManager.removeVirtualFileListener(myChangedFilesCollector);
627
628         //FileUtil.delete(getMarkerFile());
629       }
630       catch (Throwable e) {
631         LOG.info("Problems during index shutdown", e);
632         throw new RuntimeException(e);
633       }
634       LOG.info("END INDEX SHUTDOWN");
635     }
636   }
637
638   private void flushAllIndices(final long modCount) {
639     if (HeavyProcessLatch.INSTANCE.isRunning()) {
640       return;
641     }
642     IndexingStamp.flushCache();
643     for (ID<?, ?> indexId : new ArrayList<ID<?, ?>>(myIndices.keySet())) {
644       if (HeavyProcessLatch.INSTANCE.isRunning() || modCount != myLocalModCount) {
645         return; // do not interfere with 'main' jobs
646       }
647       try {
648         final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
649         if (index != null) {
650           index.flush();
651         }
652       }
653       catch (StorageException e) {
654         LOG.info(e);
655         requestRebuild(indexId);
656       }
657     }
658
659     if (!HeavyProcessLatch.INSTANCE.isRunning() && modCount == myLocalModCount) { // do not interfere with 'main' jobs
660       SerializationManager.getInstance().flushNameStorage();
661     }
662   }
663
664   /**
665    * @param project it is guaranteeed to return data which is up-to-date withing the project
666    * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist
667    */
668   @NotNull
669   public <K> Collection<K> getAllKeys(final ID<K, ?> indexId, @NotNull Project project) {
670     Set<K> allKeys = new HashSet<K>();
671     processAllKeys(indexId, new CommonProcessors.CollectProcessor<K>(allKeys), project);
672     return allKeys;
673   }
674
675   /**
676    * @param project it is guaranteeed to return data which is up-to-date withing the project
677    * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist
678    */
679   public <K> boolean processAllKeys(final ID<K, ?> indexId, Processor<K> processor, @Nullable Project project) {
680     try {
681       final UpdatableIndex<K, ?, FileContent> index = getIndex(indexId);
682       if (index == null) {
683         return true;
684       }
685       ensureUpToDate(indexId, project, project != null? GlobalSearchScope.allScope(project) : new EverythingGlobalScope());
686       return index.processAllKeys(processor);
687     }
688     catch (StorageException e) {
689       scheduleRebuild(indexId, e);
690     }
691     catch (RuntimeException e) {
692       final Throwable cause = e.getCause();
693       if (cause instanceof StorageException || cause instanceof IOException) {
694         scheduleRebuild(indexId, cause);
695       }
696       else {
697         throw e;
698       }
699     }
700
701     return false;
702   }
703
704   private static final ThreadLocal<Integer> myUpToDateCheckState = new ThreadLocal<Integer>();
705
706   public static void disableUpToDateCheckForCurrentThread() {
707     final Integer currentValue = myUpToDateCheckState.get();
708     myUpToDateCheckState.set(currentValue == null? 1 : currentValue.intValue() + 1);
709   }
710
711   public static void enableUpToDateCheckForCurrentThread() {
712     final Integer currentValue = myUpToDateCheckState.get();
713     if (currentValue != null) {
714       final int newValue = currentValue.intValue() - 1;
715       if (newValue != 0) {
716         myUpToDateCheckState.set(newValue);
717       }
718       else {
719         myUpToDateCheckState.remove();
720       }
721     }
722   }
723
724   private static boolean isUpToDateCheckEnabled() {
725     final Integer value = myUpToDateCheckState.get();
726     return value == null || value.intValue() == 0;
727   }
728
729
730   private final ThreadLocal<Boolean> myReentrancyGuard = new ThreadLocal<Boolean>() {
731     @Override
732     protected Boolean initialValue() {
733       return Boolean.FALSE;
734     }
735   };
736
737   /**
738    * DO NOT CALL DIRECTLY IN CLIENT CODE
739    * The method is internal to indexing engine end is called internally. The method is public due to implementation details
740    */
741   public <K> void ensureUpToDate(final ID<K, ?> indexId, @Nullable Project project, @Nullable GlobalSearchScope filter) {
742     ensureUpToDate(indexId, project, filter, null);
743   }
744
745   private <K> void ensureUpToDate(final ID<K, ?> indexId, @Nullable Project project, @Nullable GlobalSearchScope filter,
746                                  @Nullable VirtualFile restrictedFile) {
747     if (!needsFileContentLoading(indexId)) {
748       return; //indexed eagerly in foreground while building unindexed file list
749     }
750     if (isDumb(project)) {
751       handleDumbMode(project);
752     }
753
754     if (myReentrancyGuard.get().booleanValue()) {
755       //assert false : "ensureUpToDate() is not reentrant!";
756       return;
757     }
758     myReentrancyGuard.set(Boolean.TRUE);
759
760     try {
761       myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
762       if (isUpToDateCheckEnabled()) {
763         try {
764           checkRebuild(indexId, false);
765           myChangedFilesCollector.forceUpdate(project, filter, restrictedFile, false);
766           indexUnsavedDocuments(indexId, project, filter, restrictedFile);
767         }
768         catch (StorageException e) {
769           scheduleRebuild(indexId, e);
770         }
771         catch (RuntimeException e) {
772           final Throwable cause = e.getCause();
773           if (cause instanceof StorageException || cause instanceof IOException) {
774             scheduleRebuild(indexId, e);
775           }
776           else {
777             throw e;
778           }
779         }
780       }
781     }
782     finally {
783       myReentrancyGuard.set(Boolean.FALSE);
784     }
785   }
786
787   private static void handleDumbMode(@Nullable Project project) {
788     ProgressManager.checkCanceled(); // DumbModeAction.CANCEL
789
790     if (project != null) {
791       final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
792       if (progressIndicator instanceof BackgroundableProcessIndicator) {
793         final BackgroundableProcessIndicator indicator = (BackgroundableProcessIndicator)progressIndicator;
794         if (indicator.getDumbModeAction() == DumbModeAction.WAIT) {
795           assert !ApplicationManager.getApplication().isDispatchThread();
796           DumbService.getInstance(project).waitForSmartMode();
797           return;
798         }
799       }
800     }
801
802     throw new IndexNotReadyException();
803   }
804
805   private static boolean isDumb(@Nullable Project project) {
806     if (project != null) {
807       return DumbServiceImpl.getInstance(project).isDumb();
808     }
809     for (Project proj : ProjectManager.getInstance().getOpenProjects()) {
810       if (DumbServiceImpl.getInstance(proj).isDumb()) {
811         return true;
812       }
813     }
814     return false;
815   }
816
817   @NotNull
818   public <K, V> List<V> getValues(final ID<K, V> indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) {
819     final List<V> values = new SmartList<V>();
820     processValuesImpl(indexId, dataKey, true, null, new ValueProcessor<V>() {
821       @Override
822       public boolean process(final VirtualFile file, final V value) {
823         values.add(value);
824         return true;
825       }
826     }, filter);
827     return values;
828   }
829
830   @NotNull
831   public <K, V> Collection<VirtualFile> getContainingFiles(final ID<K, V> indexId, @NotNull K dataKey, @NotNull final GlobalSearchScope filter) {
832     final Set<VirtualFile> files = new HashSet<VirtualFile>();
833     processValuesImpl(indexId, dataKey, false, null, new ValueProcessor<V>() {
834       @Override
835       public boolean process(final VirtualFile file, final V value) {
836         files.add(file);
837         return true;
838       }
839     }, filter);
840     return files;
841   }
842
843
844   public interface ValueProcessor<V> {
845     /**
846      * @param value a value to process
847      * @param file the file the value came from
848      * @return false if no further processing is needed, true otherwise
849      */
850     boolean process(VirtualFile file, V value);
851   }
852
853   /**
854    * @return false if ValueProcessor.process() returned false; true otherwise or if ValueProcessor was not called at all 
855    */
856   public <K, V> boolean processValues(final ID<K, V> indexId, @NotNull final K dataKey, @Nullable final VirtualFile inFile,
857                                    ValueProcessor<V> processor, @NotNull final GlobalSearchScope filter) {
858     return processValuesImpl(indexId, dataKey, false, inFile, processor, filter);
859   }
860
861
862
863   private <K, V> boolean processValuesImpl(final ID<K, V> indexId, final K dataKey, boolean ensureValueProcessedOnce,
864                                         @Nullable final VirtualFile restrictToFile, ValueProcessor<V> processor,
865                                         final GlobalSearchScope filter) {
866     try {
867       final UpdatableIndex<K, V, FileContent> index = getIndex(indexId);
868       if (index == null) {
869         return true;
870       }
871       final Project project = filter.getProject();
872       //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries";
873       ensureUpToDate(indexId, project, filter, restrictToFile);
874
875       try {
876         index.getReadLock().lock();
877         final ValueContainer<V> container = index.getData(dataKey);
878
879         boolean shouldContinue = true;
880
881         if (restrictToFile != null) {
882           if (restrictToFile instanceof VirtualFileWithId) {
883             final int restrictedFileId = getFileId(restrictToFile);
884             for (final Iterator<V> valueIt = container.getValueIterator(); valueIt.hasNext();) {
885               final V value = valueIt.next();
886               if (container.isAssociated(value, restrictedFileId)) {
887                 shouldContinue = processor.process(restrictToFile, value);
888                 if (!shouldContinue) {
889                   break;
890                 }
891               }
892             }
893           }
894         }
895         else {
896           final PersistentFS fs = (PersistentFS)ManagingFS.getInstance();
897           VALUES_LOOP: for (final Iterator<V> valueIt = container.getValueIterator(); valueIt.hasNext();) {
898             final V value = valueIt.next();
899             for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) {
900               final int id = inputIdsIterator.next();
901               VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
902               if (file != null && filter.accept(file)) {
903                 shouldContinue = processor.process(file, value);
904                 if (!shouldContinue) {
905                   break VALUES_LOOP;
906                 }
907                 if (ensureValueProcessedOnce) {
908                   break; // continue with the next value
909                 }
910               }
911             }
912           }
913         }
914         return shouldContinue;
915       }
916       finally {
917         index.getReadLock().unlock();
918       }
919     }
920     catch (StorageException e) {
921       scheduleRebuild(indexId, e);
922     }
923     catch (RuntimeException e) {
924       final Throwable cause = getCauseToRebuildIndex(e);
925       if (cause != null) {
926         scheduleRebuild(indexId, cause);
927       }
928       else {
929         throw e;
930       }
931     }
932     return true;
933   }
934
935   public static @Nullable Throwable getCauseToRebuildIndex(RuntimeException e) {
936     Throwable cause = e.getCause();
937     if (cause instanceof StorageException || cause instanceof IOException ||
938         cause instanceof IllegalArgumentException || cause instanceof IllegalStateException) return cause;
939     return null;
940   }
941
942   public <K, V> boolean getFilesWithKey(final ID<K, V> indexId, final Set<K> dataKeys, Processor<VirtualFile> processor, GlobalSearchScope filter) {
943     try {
944       final UpdatableIndex<K, V, FileContent> index = getIndex(indexId);
945       if (index == null) {
946         return true;
947       }
948       final Project project = filter.getProject();
949       //assert project != null : "GlobalSearchScope#getProject() should be not-null for all index queries";
950       ensureUpToDate(indexId, project, filter);
951
952       try {
953         index.getReadLock().lock();
954         final List<TIntHashSet> locals = new ArrayList<TIntHashSet>();
955         for (K dataKey : dataKeys) {
956           TIntHashSet local = new TIntHashSet();
957           locals.add(local);
958           final ValueContainer<V> container = index.getData(dataKey);
959
960           for (final Iterator<V> valueIt = container.getValueIterator(); valueIt.hasNext();) {
961             final V value = valueIt.next();
962             for (final ValueContainer.IntIterator inputIdsIterator = container.getInputIdsIterator(value); inputIdsIterator.hasNext();) {
963               final int id = inputIdsIterator.next();
964               local.add(id);
965             }
966           }
967         }
968
969         if (locals.isEmpty()) {
970           return true;
971         }
972
973         Collections.sort(locals, new Comparator<TIntHashSet>() {
974           @Override
975           public int compare(TIntHashSet o1, TIntHashSet o2) {
976             return o1.size() - o2.size();
977           }
978         });
979
980         final PersistentFS fs = (PersistentFS)ManagingFS.getInstance();
981         TIntIterator ids = join(locals).iterator();
982         while (ids.hasNext()) {
983           int id = ids.next();
984           //VirtualFile file = IndexInfrastructure.findFileById(fs, id);
985           VirtualFile file = IndexInfrastructure.findFileByIdIfCached(fs, id);
986           if (file != null && filter.accept(file)) {
987             if (!processor.process(file)) {
988               return false;
989             }
990           }
991         }
992       }
993       finally {
994         index.getReadLock().unlock();
995       }
996     }
997     catch (StorageException e) {
998       scheduleRebuild(indexId, e);
999     }
1000     catch (RuntimeException e) {
1001       final Throwable cause = e.getCause();
1002       if (cause instanceof StorageException || cause instanceof IOException) {
1003         scheduleRebuild(indexId, cause);
1004       }
1005       else {
1006         throw e;
1007       }
1008     }
1009     return true;
1010   }
1011
1012   private static TIntHashSet join(List<TIntHashSet> locals) {
1013     TIntHashSet result = locals.get(0);
1014     if (locals.size() > 1) {
1015       TIntIterator it = result.iterator();
1016
1017       while (it.hasNext()) {
1018         int id = it.next();
1019         for (int i = 1; i < locals.size(); i++) {
1020           if (!locals.get(i).contains(id)) {
1021             it.remove();
1022             break;
1023           }
1024         }
1025       }
1026     }
1027     return result;
1028   }
1029
1030   public <K> void scheduleRebuild(final ID<K, ?> indexId, final Throwable e) {
1031     LOG.info(e);
1032     requestRebuild(indexId);
1033     try {
1034       checkRebuild(indexId, false);
1035     }
1036     catch (ProcessCanceledException ignored) {
1037     }
1038   }
1039
1040   private void checkRebuild(final ID<?, ?> indexId, final boolean cleanupOnly) {
1041     final AtomicInteger status = ourRebuildStatus.get(indexId);
1042     if (status.get() == OK) return;
1043     if (status.compareAndSet(REQUIRES_REBUILD, REBUILD_IN_PROGRESS)) {
1044       cleanupProcessedFlag();
1045
1046       final Runnable rebuildRunnable = new Runnable() {
1047         @Override
1048         public void run() {
1049           try {
1050             clearIndex(indexId);
1051             if (!cleanupOnly) {
1052               scheduleIndexRebuild(false);
1053             }
1054           }
1055           catch (StorageException e) {
1056             requestRebuild(indexId);
1057             LOG.info(e);
1058           }
1059           finally {
1060             status.compareAndSet(REBUILD_IN_PROGRESS, OK);
1061           }
1062         }
1063       };
1064
1065       if (cleanupOnly || myIsUnitTestMode) {
1066         rebuildRunnable.run();
1067       }
1068       else {
1069         SwingUtilities.invokeLater(new Runnable() {
1070           @Override
1071           public void run() {
1072             new Task.Modal(null, "Updating index", false) {
1073               @Override
1074               public void run(@NotNull final ProgressIndicator indicator) {
1075                 indicator.setIndeterminate(true);
1076                 rebuildRunnable.run();
1077               }
1078             }.queue();
1079           }
1080         });
1081       }
1082     }
1083
1084     if (status.get() == REBUILD_IN_PROGRESS) {
1085       throw new ProcessCanceledException();
1086     }
1087   }
1088
1089   private void scheduleIndexRebuild(boolean forceDumbMode) {
1090     for (Project project : ProjectManager.getInstance().getOpenProjects()) {
1091       final Set<CacheUpdater> updatersToRun = Collections.<CacheUpdater>singleton(new UnindexedFilesUpdater(project, this));
1092       final DumbServiceImpl service = DumbServiceImpl.getInstance(project);
1093       if (forceDumbMode) {
1094         service.queueCacheUpdateInDumbMode(updatersToRun);
1095       }
1096       else {
1097         service.queueCacheUpdate(updatersToRun);
1098       }
1099     }
1100   }
1101
1102   private void clearIndex(final ID<?, ?> indexId) throws StorageException {
1103     final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1104     assert index != null: "Index with key " + indexId + " not found or not registered properly";
1105     index.clear();
1106     try {
1107       IndexInfrastructure.rewriteVersion(IndexInfrastructure.getVersionFile(indexId), myIndexIdToVersionMap.get(indexId));
1108     }
1109     catch (IOException e) {
1110       LOG.error(e);
1111     }
1112   }
1113
1114   private Set<Document> getUnsavedOrTransactedDocuments() {
1115     final Set<Document> docs = new HashSet<Document>(Arrays.asList(myFileDocumentManager.getUnsavedDocuments()));
1116     synchronized (myTransactionMap) {
1117       docs.addAll(myTransactionMap.keySet());
1118     }
1119     return docs;
1120   }
1121
1122   private void indexUnsavedDocuments(ID<?, ?> indexId, @Nullable Project project, GlobalSearchScope filter,
1123                                      VirtualFile restrictedFile) throws StorageException {
1124     if (myUpToDateIndices.contains(indexId)) {
1125       return; // no need to index unsaved docs
1126     }
1127
1128     final Set<Document> documents = getUnsavedOrTransactedDocuments();
1129     if (!documents.isEmpty()) {
1130       // now index unsaved data
1131       final StorageGuard.Holder guard = setDataBufferingEnabled(true);
1132       try {
1133         final Semaphore semaphore = myUnsavedDataIndexingSemaphores.get(indexId);
1134
1135         assert semaphore != null : "Semaphore for unsaved data indexing was not initialized for index " + indexId;
1136
1137         semaphore.down();
1138         boolean allDocsProcessed = true;
1139         try {
1140           for (Document document : documents) {
1141             allDocsProcessed &= indexUnsavedDocument(document, indexId, project, filter, restrictedFile);
1142           }
1143         }
1144         finally {
1145           semaphore.up();
1146
1147           while (!semaphore.waitFor(500)) { // may need to wait until another thread is done with indexing
1148             if (Thread.holdsLock(PsiLock.LOCK)) {
1149               break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
1150             }
1151           }
1152           if (allDocsProcessed && !hasActiveTransactions()) {
1153             myUpToDateIndices.add(indexId); // safe to set the flag here, because it will be cleared under the WriteAction
1154           }
1155         }
1156       }
1157       finally {
1158         guard.leave();
1159       }
1160     }
1161   }
1162
1163   private boolean hasActiveTransactions() {
1164     synchronized (myTransactionMap) {
1165       return !myTransactionMap.isEmpty();
1166     }
1167   }
1168
1169   private interface DocumentContent {
1170     String getText();
1171     long getModificationStamp();
1172   }
1173
1174   private static class AuthenticContent implements DocumentContent {
1175     private final Document myDocument;
1176
1177     private AuthenticContent(final Document document) {
1178       myDocument = document;
1179     }
1180
1181     @Override
1182     public String getText() {
1183       return myDocument.getText();
1184     }
1185
1186     @Override
1187     public long getModificationStamp() {
1188       return myDocument.getModificationStamp();
1189     }
1190   }
1191
1192   private static class PsiContent implements DocumentContent {
1193     private final Document myDocument;
1194     private final PsiFile myFile;
1195
1196     private PsiContent(final Document document, final PsiFile file) {
1197       myDocument = document;
1198       myFile = file;
1199     }
1200
1201     @Override
1202     public String getText() {
1203       if (myFile.getModificationStamp() != myDocument.getModificationStamp()) {
1204         final ASTNode node = myFile.getNode();
1205         assert node != null;
1206         return node.getText();
1207       }
1208       return myDocument.getText();
1209     }
1210
1211     @Override
1212     public long getModificationStamp() {
1213       return myFile.getModificationStamp();
1214     }
1215   }
1216
1217 // returns false if doc was not indexed because the file does not fit in scope
1218   private boolean indexUnsavedDocument(@NotNull final Document document, @NotNull final ID<?, ?> requestedIndexId, final Project project, 
1219                                        GlobalSearchScope filter, VirtualFile restrictedFile) throws StorageException {
1220     final VirtualFile vFile = myFileDocumentManager.getFile(document);
1221     if (!(vFile instanceof VirtualFileWithId) || !vFile.isValid()) {
1222       return true;
1223     }
1224     
1225     if (restrictedFile != null) {
1226       if(vFile != restrictedFile) {
1227         return false;
1228       }
1229     } 
1230     else if (filter != null && !filter.accept(vFile)) {
1231       return false;
1232     }
1233
1234     final PsiFile dominantContentFile = findDominantPsiForDocument(document, project);
1235
1236     final DocumentContent content;
1237     if (dominantContentFile != null && dominantContentFile.getModificationStamp() != document.getModificationStamp()) {
1238       content = new PsiContent(document, dominantContentFile);
1239     }
1240     else {
1241       content = new AuthenticContent(document);
1242     }
1243
1244     final long currentDocStamp = content.getModificationStamp();
1245     if (currentDocStamp != myLastIndexedDocStamps.getAndSet(document, requestedIndexId, currentDocStamp)) {
1246       final Ref<StorageException> exRef = new Ref<StorageException>(null);
1247       ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
1248         @Override
1249         public void run() {
1250           try {
1251             final String contentText = content.getText();
1252             if (isTooLarge(vFile, contentText.length())) {
1253               return;
1254             }
1255
1256             final FileContentImpl newFc = new FileContentImpl(vFile, contentText, vFile.getCharset());
1257
1258             if (dominantContentFile != null) {
1259               dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1260               newFc.putUserData(IndexingDataKeys.PSI_FILE, dominantContentFile);
1261             }
1262
1263             if (content instanceof AuthenticContent) {
1264               newFc.putUserData(EDITOR_HIGHLIGHTER, EditorHighlighterCache.getEditorHighlighterForCachesBuilding(document));
1265             }
1266
1267             if (getInputFilter(requestedIndexId).acceptInput(vFile)) {
1268               newFc.putUserData(IndexingDataKeys.PROJECT, project);
1269               final int inputId = Math.abs(getFileId(vFile));
1270               getIndex(requestedIndexId).update(inputId, newFc);
1271             }
1272
1273             if (dominantContentFile != null) {
1274               dominantContentFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1275             }
1276           }
1277           catch (StorageException e) {
1278             exRef.set(e);
1279           }
1280         }
1281       });
1282       final StorageException storageException = exRef.get();
1283       if (storageException != null) {
1284         throw storageException;
1285       }
1286     }
1287     return true;
1288   }
1289
1290   public static final Key<EditorHighlighter> EDITOR_HIGHLIGHTER = new Key<EditorHighlighter>("Editor");
1291
1292   @Nullable
1293   private PsiFile findDominantPsiForDocument(@NotNull Document document, @Nullable Project project) {
1294     synchronized (myTransactionMap) {
1295       PsiFile psiFile = myTransactionMap.get(document);
1296       if (psiFile != null) return psiFile;
1297     }
1298
1299     return project == null ? null : findLatestKnownPsiForUncomittedDocument(document, project);
1300   }
1301
1302   private final StorageGuard myStorageLock = new StorageGuard();
1303
1304   private StorageGuard.Holder setDataBufferingEnabled(final boolean enabled) {
1305     final StorageGuard.Holder holder = myStorageLock.enter(enabled);
1306     for (ID<?, ?> indexId : myIndices.keySet()) {
1307       final MapReduceIndex index = (MapReduceIndex)getIndex(indexId);
1308       assert index != null;
1309       final IndexStorage indexStorage = index.getStorage();
1310       ((MemoryIndexStorage)indexStorage).setBufferingEnabled(enabled);
1311     }
1312     return holder;
1313   }
1314
1315   private void cleanupMemoryStorage() {
1316     myLastIndexedDocStamps.clear();
1317     for (ID<?, ?> indexId : myIndices.keySet()) {
1318       final MapReduceIndex index = (MapReduceIndex)getIndex(indexId);
1319       assert index != null;
1320       final MemoryIndexStorage memStorage = (MemoryIndexStorage)index.getStorage();
1321       index.getWriteLock().lock();
1322       try {
1323         memStorage.clearMemoryMap();
1324       }
1325       finally {
1326         index.getWriteLock().unlock();
1327       }
1328       memStorage.fireMemoryStorageCleared();
1329     }
1330   }
1331
1332   private void dropUnregisteredIndices() {
1333     final Set<String> indicesToDrop = readRegistsredIndexNames();
1334     for (ID<?, ?> key : myIndices.keySet()) {
1335       indicesToDrop.remove(key.toString());
1336     }
1337     for (String s : indicesToDrop) {
1338       FileUtil.delete(IndexInfrastructure.getIndexRootDir(ID.create(s)));
1339     }
1340   }
1341
1342   public static void requestRebuild(ID<?, ?> indexId) {
1343     requestRebuild(indexId, new Throwable());
1344   }
1345
1346   public static void requestRebuild(ID<?, ?> indexId, Throwable throwable) {
1347     cleanupProcessedFlag();
1348     LOG.info("Rebuild requested for index " + indexId, throwable);
1349     ourRebuildStatus.get(indexId).set(REQUIRES_REBUILD);
1350   }
1351
1352   private <K, V> UpdatableIndex<K, V, FileContent> getIndex(ID<K, V> indexId) {
1353     final Pair<UpdatableIndex<?, ?, FileContent>, InputFilter> pair = myIndices.get(indexId);
1354     
1355     assert pair != null : "Index data is absent for index " + indexId;
1356
1357     //noinspection unchecked
1358     return (UpdatableIndex<K,V, FileContent>)pair.getFirst();
1359   }
1360
1361   private InputFilter getInputFilter(ID<?, ?> indexId) {
1362     final Pair<UpdatableIndex<?, ?, FileContent>, InputFilter> pair = myIndices.get(indexId);
1363     
1364     assert pair != null : "Index data is absent for index " + indexId;
1365
1366     return pair.getSecond();
1367   }
1368
1369   public int getNumberOfPendingInvalidations() {
1370     return myChangedFilesCollector.getNumberOfPendingInvalidations();
1371   }
1372
1373   public Collection<VirtualFile> getFilesToUpdate(final Project project) {
1374     return ContainerUtil.findAll(myChangedFilesCollector.getAllFilesToUpdate(), new Condition<VirtualFile>() {
1375       @Override
1376       public boolean value(VirtualFile virtualFile) {
1377         for (IndexableFileSet set : myIndexableSets) {
1378           final Project proj = myIndexableSetToProjectMap.get(set);
1379           if (proj != null && !proj.equals(project)) {
1380             continue; // skip this set as associated with a different project
1381           }
1382           if (set.isInSet(virtualFile)) {
1383             return true;
1384           }
1385         }
1386         return false;
1387       }
1388     });
1389   }
1390
1391   public void processRefreshedFile(@NotNull Project project, final com.intellij.ide.caches.FileContent fileContent) {
1392     myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
1393     myChangedFilesCollector.processFileImpl(project, fileContent, false);
1394   }
1395
1396   public void indexFileContent(@Nullable Project project, com.intellij.ide.caches.FileContent content) {
1397     myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
1398     final VirtualFile file = content.getVirtualFile();
1399     FileContentImpl fc = null;
1400
1401     PsiFile psiFile = null;
1402
1403     for (final ID<?, ?> indexId : myIndices.keySet()) {
1404       if (shouldIndexFile(file, indexId)) {
1405         if (fc == null) {
1406           byte[] currentBytes;
1407           try {
1408             currentBytes = content.getBytes();
1409           }
1410           catch (IOException e) {
1411             currentBytes = ArrayUtil.EMPTY_BYTE_ARRAY;
1412           }
1413           fc = new FileContentImpl(file, currentBytes);
1414
1415           psiFile = content.getUserData(IndexingDataKeys.PSI_FILE);
1416           if (psiFile != null) {
1417             psiFile.putUserData(PsiFileImpl.BUILDING_STUB, true);
1418             fc.putUserData(IndexingDataKeys.PSI_FILE, psiFile);
1419           }
1420           if (project == null) {
1421             project = ProjectUtil.guessProjectForFile(file);
1422           }
1423           fc.putUserData(IndexingDataKeys.PROJECT, project);
1424         }
1425
1426         try {
1427           ProgressManager.checkCanceled();
1428           updateSingleIndex(indexId, file, fc);
1429         }
1430         catch (ProcessCanceledException e) {
1431           myChangedFilesCollector.scheduleForUpdate(file);
1432           throw e;
1433         }
1434         catch (StorageException e) {
1435           requestRebuild(indexId);
1436           LOG.info(e);
1437         }
1438       }
1439     }
1440
1441     if (psiFile != null) {
1442       psiFile.putUserData(PsiFileImpl.BUILDING_STUB, null);
1443     }
1444   }
1445
1446   private void updateSingleIndex(final ID<?, ?> indexId, final VirtualFile file, final FileContent currentFC)
1447     throws StorageException {
1448     if (ourRebuildStatus.get(indexId).get() == REQUIRES_REBUILD) {
1449       return; // the index is scheduled for rebuild, no need to update
1450     }
1451     myLocalModCount++;
1452
1453     final StorageGuard.Holder lock = setDataBufferingEnabled(false);
1454
1455     try {
1456       final int inputId = Math.abs(getFileId(file));
1457
1458       final UpdatableIndex<?, ?, FileContent> index = getIndex(indexId);
1459       assert index != null;
1460
1461       final Ref<StorageException> exRef = new Ref<StorageException>(null);
1462       ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
1463         @Override
1464         public void run() {
1465           try {
1466             index.update(inputId, currentFC);
1467           }
1468           catch (StorageException e) {
1469             exRef.set(e);
1470           }
1471         }
1472       });
1473       final StorageException storageException = exRef.get();
1474       if (storageException != null) {
1475         throw storageException;
1476       }
1477       ApplicationManager.getApplication().runReadAction(new Runnable() {
1478         @Override
1479         public void run() {
1480           if (file.isValid()) {
1481             if (currentFC != null) {
1482               IndexingStamp.update(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId));
1483             }
1484             else {
1485               // mark the file as unindexed
1486               IndexingStamp.update(file, indexId, -1L);
1487             }
1488           }
1489         }
1490       });
1491     }
1492     finally {
1493       lock.leave();
1494     }
1495   }
1496
1497   public static int getFileId(final VirtualFile file) {
1498     if (file instanceof VirtualFileWithId) {
1499       return ((VirtualFileWithId)file).getId();
1500     }
1501
1502     throw new IllegalArgumentException("Virtual file doesn't support id: " + file + ", implementation class: " + file.getClass().getName());
1503   }
1504
1505   private boolean needsFileContentLoading(ID<?, ?> indexId) {
1506     return !myNotRequiringContentIndices.contains(indexId);
1507   }
1508
1509   private abstract static class InvalidationTask implements Runnable {
1510     private final VirtualFile mySubj;
1511
1512     protected InvalidationTask(final VirtualFile subj) {
1513       mySubj = subj;
1514     }
1515
1516     public VirtualFile getSubj() {
1517       return mySubj;
1518     }
1519   }
1520
1521   private final class ChangedFilesCollector extends VirtualFileAdapter {
1522     private final Set<VirtualFile> myFilesToUpdate = new ConcurrentHashSet<VirtualFile>();
1523     private final Queue<InvalidationTask> myFutureInvalidations = new ConcurrentLinkedQueue<InvalidationTask>();
1524
1525     private final ManagingFS myManagingFS = ManagingFS.getInstance();
1526     // No need to react on movement events since files stay valid, their ids don't change and all associated attributes remain intact.
1527
1528     @Override
1529     public void fileCreated(final VirtualFileEvent event) {
1530       markDirty(event);
1531     }
1532
1533     @Override
1534     public void fileDeleted(final VirtualFileEvent event) {
1535       myFilesToUpdate.remove(event.getFile()); // no need to update it anymore
1536     }
1537
1538     @Override
1539     public void fileCopied(final VirtualFileCopyEvent event) {
1540       markDirty(event);
1541     }
1542
1543     @Override
1544     public void beforeFileDeletion(final VirtualFileEvent event) {
1545       invalidateIndices(event.getFile(), false);
1546     }
1547
1548     @Override
1549     public void beforeContentsChange(final VirtualFileEvent event) {
1550       invalidateIndices(event.getFile(), true);
1551     }
1552
1553     @Override
1554     public void contentsChanged(final VirtualFileEvent event) {
1555       markDirty(event);
1556     }
1557
1558     @Override
1559     public void beforePropertyChange(final VirtualFilePropertyEvent event) {
1560       if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
1561         // indexes may depend on file name
1562         final VirtualFile file = event.getFile();
1563         if (!file.isDirectory()) {
1564           // name change may lead to filetype change so the file might become not indexable
1565           // in general case have to 'unindex' the file and index it again if needed after the name has been changed
1566           invalidateIndices(file, false);
1567         }
1568       }
1569     }
1570
1571     @Override
1572     public void propertyChanged(final VirtualFilePropertyEvent event) {
1573       if (event.getPropertyName().equals(VirtualFile.PROP_NAME)) {
1574         // indexes may depend on file name
1575         if (!event.getFile().isDirectory()) {
1576           markDirty(event);
1577         }
1578       }
1579     }
1580
1581     private void markDirty(final VirtualFileEvent event) {
1582       final VirtualFile eventFile = event.getFile();
1583       cleanProcessedFlag(eventFile);
1584       iterateIndexableFiles(eventFile, new Processor<VirtualFile>() {
1585         @Override
1586         public boolean process(final VirtualFile file) {
1587           FileContent fileContent = null;
1588           // handle 'content-less' indices separately
1589           for (ID<?, ?> indexId : myNotRequiringContentIndices) {
1590             if (getInputFilter(indexId).acceptInput(file)) {
1591               try {
1592                 if (fileContent == null) {
1593                   fileContent = new FileContentImpl(file);
1594                 }
1595                 updateSingleIndex(indexId, file, fileContent);
1596               }
1597               catch (StorageException e) {
1598                 LOG.info(e);
1599                 requestRebuild(indexId);
1600               }
1601             }
1602           }
1603           // For 'normal indices' schedule the file for update and stop iteration if at least one index accepts it 
1604           if (!isTooLarge(file)) {
1605             for (ID<?, ?> indexId : myIndices.keySet()) {
1606               if (needsFileContentLoading(indexId) && getInputFilter(indexId).acceptInput(file)) {
1607                 scheduleForUpdate(file);
1608                 break; // no need to iterate further, as the file is already marked
1609               }
1610             }
1611           }
1612
1613           return true;
1614         }
1615       });
1616       IndexingStamp.flushCache();
1617     }
1618
1619     public void scheduleForUpdate(VirtualFile file) {
1620       myFilesToUpdate.add(file);
1621     }
1622
1623     void invalidateIndices(final VirtualFile file, final boolean markForReindex) {
1624       if (isUnderConfigOrSystem(file)) {
1625         return;
1626       }
1627       if (file.isDirectory()) {
1628         if (isMock(file) || myManagingFS.wereChildrenAccessed(file)) {
1629           final Iterable<VirtualFile> children = file instanceof NewVirtualFile
1630                                                  ? ((NewVirtualFile)file).iterInDbChildren() : Arrays.asList(file.getChildren());
1631           for (VirtualFile child : children) {
1632             invalidateIndices(child, markForReindex);
1633           }
1634         }
1635       }
1636       else {
1637         cleanProcessedFlag(file);
1638         IndexingStamp.flushCache();
1639         final List<ID<?, ?>> affectedIndices = new ArrayList<ID<?, ?>>(myIndices.size());
1640
1641         for (final ID<?, ?> indexId : myIndices.keySet()) {
1642           try {
1643             if (!needsFileContentLoading(indexId)) {
1644               if (shouldUpdateIndex(file, indexId)) {
1645                 updateSingleIndex(indexId, file, null);
1646               }
1647             }
1648             else { // the index requires file content
1649               if (shouldUpdateIndex(file, indexId)) {
1650                 affectedIndices.add(indexId);
1651               }
1652             }
1653           }
1654           catch (StorageException e) {
1655             LOG.info(e);
1656             requestRebuild(indexId);
1657           }
1658         }
1659
1660         if (!affectedIndices.isEmpty()) {
1661           if (markForReindex && !isTooLarge(file)) {
1662             // only mark the file as unindexed, reindex will be done lazily
1663             ApplicationManager.getApplication().runReadAction(new Runnable() {
1664               @Override
1665               public void run() {
1666                 for (ID<?, ?> indexId : affectedIndices) {
1667                   IndexingStamp.update(file, indexId, -2L);
1668                 }
1669               }
1670             });
1671             // the file is for sure not a dir and it was previously indexed by at least one index
1672             scheduleForUpdate(file);
1673           }
1674           else {
1675             myFutureInvalidations.offer(new InvalidationTask(file) {
1676               @Override
1677               public void run() {
1678                 removeFileDataFromIndices(affectedIndices, file);
1679               }
1680             });
1681           }
1682         }
1683         if (!markForReindex) {
1684           final boolean removedFromUpdateQueue = myFilesToUpdate.remove(file);// no need to update it anymore
1685           if (removedFromUpdateQueue && affectedIndices.isEmpty()) {
1686             // Currently the file is about to be deleted and previously it was scheduled for update and not processed up to now.
1687             // Because the file was scheduled for update, at the moment of scheduling it was marked as unindexed, 
1688             // so, to be on the safe side, we have to schedule data invalidation from all content-requiring indices for this file
1689             myFutureInvalidations.offer(new InvalidationTask(file) {
1690               @Override
1691               public void run() {
1692                 removeFileDataFromIndices(myRequiringContentIndices, file);
1693               }
1694             });
1695           }
1696         }
1697         
1698         IndexingStamp.flushCache();
1699       }
1700     }
1701
1702     private void removeFileDataFromIndices(Collection<ID<?, ?>> affectedIndices, VirtualFile file) {
1703       Throwable unexpectedError = null;
1704       for (ID<?, ?> indexId : affectedIndices) {
1705         try {
1706           updateSingleIndex(indexId, file, null);
1707         }
1708         catch (StorageException e) {
1709           LOG.info(e);
1710           requestRebuild(indexId);
1711         }
1712         catch (ProcessCanceledException ignored) {
1713         }
1714         catch (Throwable e) {
1715           LOG.info(e);
1716           if (unexpectedError == null) {
1717             unexpectedError = e;
1718           }
1719         }
1720       }
1721       IndexingStamp.flushCache();
1722       if (unexpectedError != null) {
1723         LOG.error(unexpectedError);
1724       }
1725     }
1726
1727     public int getNumberOfPendingInvalidations() {
1728       return myFutureInvalidations.size();
1729     }
1730
1731     public void ensureAllInvalidateTasksCompleted() {
1732       final int size = getNumberOfPendingInvalidations();
1733       if (size == 0) {
1734         return;
1735       }
1736       final ProgressIndicator current = ProgressManager.getInstance().getProgressIndicator();
1737       final ProgressIndicator indicator = current != null ? current : new EmptyProgressIndicator();
1738       indicator.setText("");
1739       int count = 0;
1740       while (true) {
1741         InvalidationTask task = myFutureInvalidations.poll();
1742
1743         if (task == null) {
1744           break;
1745         }
1746         indicator.setFraction((double)count++ /size);
1747         indicator.setText2(task.getSubj().getPresentableUrl());
1748         task.run();
1749       }
1750     }
1751
1752     private void iterateIndexableFiles(final VirtualFile file, final Processor<VirtualFile> processor) {
1753       if (file.isDirectory()) {
1754         final ContentIterator iterator = new ContentIterator() {
1755           @Override
1756           public boolean processFile(final VirtualFile fileOrDir) {
1757             if (!fileOrDir.isDirectory()) {
1758               processor.process(fileOrDir);
1759             }
1760             return true;
1761           }
1762         };
1763
1764         for (IndexableFileSet set : myIndexableSets) {
1765           set.iterateIndexableFilesIn(file, iterator);
1766         }
1767       }
1768       else {
1769         for (IndexableFileSet set : myIndexableSets) {
1770           if (set.isInSet(file)) {
1771             processor.process(file);
1772             break;
1773           }
1774         }
1775       }
1776     }
1777
1778     public Collection<VirtualFile> getAllFilesToUpdate() {
1779       if (myFilesToUpdate.isEmpty()) {
1780         return Collections.emptyList();
1781       }
1782       return new ArrayList<VirtualFile>(myFilesToUpdate);
1783     }
1784
1785     private final Semaphore myForceUpdateSemaphore = new Semaphore();
1786
1787     private void forceUpdate(@Nullable Project project, @Nullable GlobalSearchScope filter, @Nullable VirtualFile restrictedTo, 
1788                              boolean onlyRemoveOutdatedData) {
1789       myChangedFilesCollector.ensureAllInvalidateTasksCompleted();
1790       for (VirtualFile file: getAllFilesToUpdate()) {
1791         if (filter == null || filter.accept(file) || file == restrictedTo) {
1792           try {
1793             myForceUpdateSemaphore.down();
1794             // process only files that can affect result
1795             processFileImpl(project, new com.intellij.ide.caches.FileContent(file), onlyRemoveOutdatedData);
1796           }
1797           finally {
1798             myForceUpdateSemaphore.up();
1799           }
1800         }
1801       }
1802
1803       // If several threads entered the method at the same time and there were files to update,
1804       // all the threads should leave the method synchronously after all the files scheduled for update are reindexed,
1805       // no matter which thread will do reindexing job.
1806       // Thus we ensure that all the threads that entered the method will get the most recent data
1807
1808       while (!myForceUpdateSemaphore.waitFor(500)) { // may need to wait until another thread is done with indexing
1809         if (Thread.holdsLock(PsiLock.LOCK)) {
1810           break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
1811         }
1812       }
1813     }
1814
1815     private void processFileImpl(Project project, final com.intellij.ide.caches.FileContent fileContent, boolean onlyRemoveOutdatedData) {
1816       final VirtualFile file = fileContent.getVirtualFile();
1817       final boolean reallyRemoved = myFilesToUpdate.remove(file);
1818       if (reallyRemoved && file.isValid()) {
1819         if (onlyRemoveOutdatedData) {
1820           // on shutdown there is no need to re-index the file, just remove outdated data from indices
1821           final List<ID<?, ?>> affected = new ArrayList<ID<?,?>>();
1822           for (final ID<?, ?> indexId : myIndices.keySet()) {
1823             if (getInputFilter(indexId).acceptInput(file)) {
1824               affected.add(indexId);
1825             }
1826           }
1827           removeFileDataFromIndices(affected, file);
1828         }
1829         else {
1830           indexFileContent(project, fileContent);
1831         }
1832         IndexingStamp.flushCache();
1833       }
1834     }
1835   }
1836
1837   private class UnindexedFilesFinder implements CollectingContentIterator {
1838     private final List<VirtualFile> myFiles = new ArrayList<VirtualFile>();
1839     private final ProgressIndicator myProgressIndicator;
1840
1841     private UnindexedFilesFinder() {
1842       myProgressIndicator = ProgressManager.getInstance().getProgressIndicator();
1843     }
1844
1845     @Override
1846     public List<VirtualFile> getFiles() {
1847       return myFiles;
1848     }
1849
1850     @Override
1851     public boolean processFile(final VirtualFile file) {
1852       if (!file.isDirectory()) {
1853         if (file instanceof NewVirtualFile && ((NewVirtualFile)file).getFlag(ALREADY_PROCESSED)) {
1854           return true;
1855         }
1856
1857         if (file instanceof VirtualFileWithId) {
1858           try {
1859             FileTypeManagerImpl.cacheFileType(file, file.getFileType());
1860
1861             boolean oldStuff = true;
1862             if (!isTooLarge(file)) {
1863               for (ID<?, ?> indexId : myIndices.keySet()) {
1864                 try {
1865                   if (needsFileContentLoading(indexId) && shouldIndexFile(file, indexId)) {
1866                     myFiles.add(file);
1867                     oldStuff = false;
1868                     break;
1869                   }
1870                 }
1871                 catch (RuntimeException e) {
1872                   final Throwable cause = e.getCause();
1873                   if (cause instanceof IOException || cause instanceof StorageException) {
1874                     LOG.info(e);
1875                     requestRebuild(indexId);
1876                   }
1877                   else {
1878                     throw e;
1879                   }
1880                 }
1881               }
1882             }
1883             FileContent fileContent = null;
1884             for (ID<?, ?> indexId : myNotRequiringContentIndices) {
1885               if (shouldIndexFile(file, indexId)) {
1886                 oldStuff = false;
1887                 try {
1888                   if (fileContent == null) {
1889                     fileContent = new FileContentImpl(file);
1890                   }
1891                   updateSingleIndex(indexId, file, fileContent);
1892                 }
1893                 catch (StorageException e) {
1894                   LOG.info(e);
1895                   requestRebuild(indexId);
1896                 }
1897               }
1898             }
1899             IndexingStamp.flushCache();
1900
1901             if (oldStuff && file instanceof NewVirtualFile) {
1902               ((NewVirtualFile)file).setFlag(ALREADY_PROCESSED, true);
1903             }
1904           }
1905           finally {
1906             FileTypeManagerImpl.cacheFileType(file, null);
1907           }
1908         }
1909       }
1910       else {
1911         if (myProgressIndicator != null) {
1912           myProgressIndicator.setText("Scanning files to index");
1913           myProgressIndicator.setText2(file.getPresentableUrl());
1914         }
1915       }
1916       return true;
1917     }
1918   }
1919
1920   private boolean shouldUpdateIndex(final VirtualFile file, final ID<?, ?> indexId) {
1921     return getInputFilter(indexId).acceptInput(file) &&
1922            (isMock(file) || IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)));
1923   }
1924
1925   private boolean shouldIndexFile(final VirtualFile file, final ID<?, ?> indexId) {
1926     return getInputFilter(indexId).acceptInput(file) &&
1927            (isMock(file) || !IndexingStamp.isFileIndexed(file, indexId, IndexInfrastructure.getIndexCreationStamp(indexId)));
1928   }
1929
1930   private boolean isUnderConfigOrSystem(VirtualFile file) {
1931     final String filePath = file.getPath();
1932     return myConfigPath != null && FileUtil.startsWith(filePath, myConfigPath) ||
1933            mySystemPath != null && FileUtil.startsWith(filePath, mySystemPath);
1934   }
1935
1936   private static boolean isMock(final VirtualFile file) {
1937     return !(file instanceof NewVirtualFile);
1938   }
1939
1940   private boolean isTooLarge(VirtualFile file) {
1941     if (SingleRootFileViewProvider.isTooLarge(file)) {
1942       final FileType type = file.getFileType();
1943       return !myNoLimitCheckTypes.contains(type);
1944     }
1945     return false;
1946   }
1947
1948   private boolean isTooLarge(VirtualFile file, long contentSize) {
1949     if (SingleRootFileViewProvider.isTooLarge(file, contentSize)) {
1950       final FileType type = file.getFileType();
1951       return !myNoLimitCheckTypes.contains(type);
1952     }
1953     return false;
1954   }
1955
1956   public CollectingContentIterator createContentIterator() {
1957     return new UnindexedFilesFinder();
1958   }
1959
1960   public void registerIndexableSet(IndexableFileSet set, @Nullable Project project) {
1961     myIndexableSets.add(set);
1962     myIndexableSetToProjectMap.put(set, project);
1963   }
1964
1965   public void removeIndexableSet(IndexableFileSet set) {
1966     myChangedFilesCollector.forceUpdate(null, null, null, true);
1967     myIndexableSets.remove(set);
1968     myIndexableSetToProjectMap.remove(set);
1969   }
1970
1971   @Nullable
1972   private static PsiFile findLatestKnownPsiForUncomittedDocument(@NotNull Document doc, @NotNull Project project) {
1973     return PsiDocumentManager.getInstance(project).getCachedPsiFile(doc);
1974   }
1975   
1976   private static class IndexableFilesFilter implements InputFilter {
1977     private final InputFilter myDelegate;
1978
1979     private IndexableFilesFilter(InputFilter delegate) {
1980       myDelegate = delegate;
1981     }
1982
1983     @Override
1984     public boolean acceptInput(final VirtualFile file) {
1985       return file instanceof VirtualFileWithId && myDelegate.acceptInput(file);
1986     }
1987   }
1988
1989   private static void cleanupProcessedFlag() {
1990     final VirtualFile[] roots = ManagingFS.getInstance().getRoots();
1991     for (VirtualFile root : roots) {
1992       cleanProcessedFlag(root);
1993     }
1994   }
1995
1996   private static void cleanProcessedFlag(final VirtualFile file) {
1997     if (!(file instanceof NewVirtualFile)) return;
1998     
1999     final NewVirtualFile nvf = (NewVirtualFile)file;
2000     if (file.isDirectory()) {
2001       for (VirtualFile child : nvf.getCachedChildren()) {
2002         cleanProcessedFlag(child);
2003       }
2004     }
2005     else {
2006       nvf.setFlag(ALREADY_PROCESSED, false);
2007     }
2008   }
2009
2010   public static void iterateIndexableFiles(final ContentIterator processor, Project project, ProgressIndicator indicator) {
2011     if (project.isDisposed()) {
2012       return;
2013     }
2014     final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
2015     // iterate project content
2016     projectFileIndex.iterateContent(processor);
2017
2018     if (project.isDisposed()) {
2019       return;
2020     }
2021
2022     Set<VirtualFile> visitedRoots = new HashSet<VirtualFile>();
2023     for (IndexedRootsProvider provider : Extensions.getExtensions(IndexedRootsProvider.EP_NAME)) {
2024       //important not to depend on project here, to support per-project background reindex
2025       // each client gives a project to FileBasedIndex
2026       if (project.isDisposed()) {
2027         return;
2028       }
2029       for (VirtualFile root : IndexableSetContributor.getRootsToIndex(provider)) {
2030         if (visitedRoots.add(root)) {
2031           iterateRecursively(root, processor, indicator);
2032         }
2033       }
2034       for (VirtualFile root : IndexableSetContributor.getProjectRootsToIndex(provider, project)) {
2035         if (visitedRoots.add(root)) {
2036           iterateRecursively(root, processor, indicator);
2037         }
2038       }
2039     }
2040
2041     if (project.isDisposed()) {
2042       return;
2043     }
2044     // iterate associated libraries
2045     for (Module module : ModuleManager.getInstance(project).getModules()) {
2046       if (module.isDisposed()) {
2047         return;
2048       }
2049       OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries();
2050       for (OrderEntry orderEntry : orderEntries) {
2051         if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) {
2052           if (orderEntry.isValid()) {
2053             final VirtualFile[] libSources = orderEntry.getFiles(OrderRootType.SOURCES);
2054             final VirtualFile[] libClasses = orderEntry.getFiles(OrderRootType.CLASSES);
2055             for (VirtualFile[] roots : new VirtualFile[][]{libSources, libClasses}) {
2056               for (VirtualFile root : roots) {
2057                 if (visitedRoots.add(root)) {
2058                   iterateRecursively(root, processor, indicator);
2059                 }
2060               }
2061             }
2062           }
2063         }
2064       }
2065     }
2066   }
2067
2068   private static void iterateRecursively(@Nullable final VirtualFile root, final ContentIterator processor, ProgressIndicator indicator) {
2069     if (root != null) {
2070       if (indicator != null) {
2071         indicator.checkCanceled();
2072         indicator.setText2(root.getPresentableUrl());
2073       }
2074
2075       if (root.isDirectory()) {
2076         for (VirtualFile file : root.getChildren()) {
2077           if (file.isDirectory()) {
2078             iterateRecursively(file, processor, indicator);
2079           }
2080           else {
2081             processor.processFile(file);
2082           }
2083         }
2084       }
2085       else {
2086         processor.processFile(root);
2087       }
2088     }
2089   }
2090
2091   private static class StorageGuard {
2092     private int myHolds = 0;
2093
2094     public interface Holder {
2095       void leave();
2096     }
2097
2098     private final Holder myTrueHolder = new Holder() {
2099       @Override
2100       public void leave() {
2101         StorageGuard.this.leave(true);
2102       }
2103     };
2104     private final Holder myFalseHolder = new Holder() {
2105       @Override
2106       public void leave() {
2107         StorageGuard.this.leave(false);
2108       }
2109     };
2110
2111     public synchronized Holder enter(boolean mode) {
2112       if (mode) {
2113         while (myHolds < 0) {
2114           try {
2115             wait();
2116           }
2117           catch (InterruptedException ignored) {
2118           }
2119         }
2120         myHolds++;
2121         return myTrueHolder;
2122       }
2123       else {
2124         while (myHolds > 0) {
2125           try {
2126             wait();
2127           }
2128           catch (InterruptedException ignored) {
2129           }
2130         }
2131         myHolds--;
2132         return myFalseHolder;
2133       }
2134     }
2135
2136     private synchronized void leave(boolean mode) {
2137       myHolds += mode? -1 : 1;
2138       if (myHolds == 0) {
2139         notifyAll();
2140       }
2141     }
2142
2143   }
2144 }