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