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