2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.intellij
.util
.indexing
;
19 import com
.intellij
.AppTopics
;
20 import com
.intellij
.concurrency
.Job
;
21 import com
.intellij
.concurrency
.JobScheduler
;
22 import com
.intellij
.ide
.startup
.BackgroundableCacheUpdater
;
23 import com
.intellij
.ide
.startup
.impl
.FileSystemSynchronizerImpl
;
24 import com
.intellij
.lang
.ASTNode
;
25 import com
.intellij
.openapi
.application
.*;
26 import com
.intellij
.openapi
.components
.ApplicationComponent
;
27 import com
.intellij
.openapi
.diagnostic
.Logger
;
28 import com
.intellij
.openapi
.editor
.Document
;
29 import com
.intellij
.openapi
.editor
.highlighter
.EditorHighlighter
;
30 import com
.intellij
.openapi
.editor
.impl
.DocumentImpl
;
31 import com
.intellij
.openapi
.extensions
.Extensions
;
32 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
33 import com
.intellij
.openapi
.fileTypes
.*;
34 import com
.intellij
.openapi
.progress
.*;
35 import com
.intellij
.openapi
.progress
.impl
.BackgroundableProcessIndicator
;
36 import com
.intellij
.openapi
.project
.*;
37 import com
.intellij
.openapi
.roots
.CollectingContentIterator
;
38 import com
.intellij
.openapi
.roots
.ContentIterator
;
39 import com
.intellij
.openapi
.roots
.ProjectRootManager
;
40 import com
.intellij
.openapi
.util
.*;
41 import com
.intellij
.openapi
.util
.io
.FileUtil
;
42 import com
.intellij
.openapi
.util
.registry
.Registry
;
43 import com
.intellij
.openapi
.vfs
.*;
44 import com
.intellij
.openapi
.vfs
.ex
.VirtualFileManagerEx
;
45 import com
.intellij
.openapi
.vfs
.newvfs
.BulkFileListener
;
46 import com
.intellij
.openapi
.vfs
.newvfs
.ManagingFS
;
47 import com
.intellij
.openapi
.vfs
.newvfs
.NewVirtualFile
;
48 import com
.intellij
.openapi
.vfs
.newvfs
.events
.VFileEvent
;
49 import com
.intellij
.openapi
.vfs
.newvfs
.impl
.NullVirtualFile
;
50 import com
.intellij
.openapi
.vfs
.newvfs
.persistent
.PersistentFS
;
51 import com
.intellij
.psi
.PsiDocumentManager
;
52 import com
.intellij
.psi
.PsiFile
;
53 import com
.intellij
.psi
.PsiLock
;
54 import com
.intellij
.psi
.SingleRootFileViewProvider
;
55 import com
.intellij
.psi
.impl
.PsiDocumentTransactionListener
;
56 import com
.intellij
.psi
.impl
.source
.PsiFileImpl
;
57 import com
.intellij
.psi
.search
.GlobalSearchScope
;
58 import com
.intellij
.util
.ArrayUtil
;
59 import com
.intellij
.util
.CommonProcessors
;
60 import com
.intellij
.util
.Processor
;
61 import com
.intellij
.util
.concurrency
.JBLock
;
62 import com
.intellij
.util
.concurrency
.JBReentrantReadWriteLock
;
63 import com
.intellij
.util
.concurrency
.LockFactory
;
64 import com
.intellij
.util
.concurrency
.Semaphore
;
65 import com
.intellij
.util
.containers
.ConcurrentHashSet
;
66 import com
.intellij
.util
.containers
.ContainerUtil
;
67 import com
.intellij
.util
.io
.*;
68 import com
.intellij
.util
.messages
.MessageBus
;
69 import com
.intellij
.util
.messages
.MessageBusConnection
;
70 import gnu
.trove
.TIntHashSet
;
71 import gnu
.trove
.TIntIterator
;
72 import gnu
.trove
.TObjectIntHashMap
;
73 import org
.jetbrains
.annotations
.NonNls
;
74 import org
.jetbrains
.annotations
.NotNull
;
75 import org
.jetbrains
.annotations
.Nullable
;
76 import org
.jetbrains
.annotations
.TestOnly
;
81 import java
.util
.concurrent
.atomic
.AtomicBoolean
;
82 import java
.util
.concurrent
.atomic
.AtomicInteger
;
83 import java
.util
.concurrent
.locks
.Lock
;
86 * @author Eugene Zhuravlev
90 public class FileBasedIndex
implements ApplicationComponent
{
91 private static final Logger LOG
= Logger
.getInstance("#com.intellij.util.indexing.FileBasedIndex");
93 private static final String CORRUPTION_MARKER_NAME
= "corruption.marker";
94 private final Map
<ID
<?
, ?
>, Pair
<UpdatableIndex
<?
, ?
, FileContent
>, InputFilter
>> myIndices
= new HashMap
<ID
<?
, ?
>, Pair
<UpdatableIndex
<?
, ?
, FileContent
>, InputFilter
>>();
95 private final Map
<ID
<?
, ?
>, Semaphore
> myUnsavedDataIndexingSemaphores
= new HashMap
<ID
<?
,?
>, Semaphore
>();
96 private final TObjectIntHashMap
<ID
<?
, ?
>> myIndexIdToVersionMap
= new TObjectIntHashMap
<ID
<?
, ?
>>();
97 private final Set
<ID
<?
, ?
>> myNotRequiringContentIndices
= new HashSet
<ID
<?
, ?
>>();
98 private final Set
<FileType
> myNoLimitCheckTypes
= new HashSet
<FileType
>();
100 private final PerIndexDocumentMap
<Long
> myLastIndexedDocStamps
= new PerIndexDocumentMap
<Long
>() {
102 protected Long
createDefault(Document document
) {
107 private final ChangedFilesUpdater myChangedFilesUpdater
;
109 private final List
<IndexableFileSet
> myIndexableSets
= ContainerUtil
.createEmptyCOWList();
111 public static final int OK
= 1;
112 public static final int REQUIRES_REBUILD
= 2;
113 public static final int REBUILD_IN_PROGRESS
= 3;
114 private final Map
<ID
<?
, ?
>, AtomicInteger
> myRebuildStatus
= new HashMap
<ID
<?
,?
>, AtomicInteger
>();
116 private final VirtualFileManagerEx myVfManager
;
117 private final FileDocumentManager myFileDocumentManager
;
118 private final ConcurrentHashSet
<ID
<?
, ?
>> myUpToDateIndices
= new ConcurrentHashSet
<ID
<?
, ?
>>();
119 private final Map
<Document
, PsiFile
> myTransactionMap
= new HashMap
<Document
, PsiFile
>();
121 private static final int ALREADY_PROCESSED
= 0x02;
122 private static final String USE_MULTITHREADED_INDEXING
= "fileIndex.multithreaded";
123 private @Nullable String myConfigPath
;
124 private @Nullable String mySystemPath
;
126 public void requestReindex(final VirtualFile file
) {
127 myChangedFilesUpdater
.invalidateIndices(file
, true);
130 public interface InputFilter
{
131 boolean acceptInput(VirtualFile file
);
134 public FileBasedIndex(final VirtualFileManagerEx vfManager
, FileDocumentManager fdm
, MessageBus bus
) throws IOException
{
135 myVfManager
= vfManager
;
136 myFileDocumentManager
= fdm
;
138 myConfigPath
= calcConfigPath(PathManager
.getConfigPath());
139 mySystemPath
= calcConfigPath(PathManager
.getSystemPath());
141 final MessageBusConnection connection
= bus
.connect();
142 connection
.subscribe(PsiDocumentTransactionListener
.TOPIC
, new PsiDocumentTransactionListener() {
143 public void transactionStarted(final Document doc
, final PsiFile file
) {
145 myTransactionMap
.put(doc
, file
);
146 myUpToDateIndices
.clear();
150 public void transactionCompleted(final Document doc
, final PsiFile file
) {
151 myTransactionMap
.remove(doc
);
155 connection
.subscribe(AppTopics
.FILE_TYPES
, new FileTypeListener() {
156 private Map
<FileType
, Set
<String
>> myTypeToExtensionMap
;
157 public void beforeFileTypesChanged(final FileTypeEvent event
) {
158 cleanupProcessedFlag();
159 myTypeToExtensionMap
= new HashMap
<FileType
, Set
<String
>>();
160 final FileTypeManager manager
= event
.getManager();
161 for (FileType type
: manager
.getRegisteredFileTypes()) {
162 myTypeToExtensionMap
.put(type
, getExtensions(manager
, type
));
166 public void fileTypesChanged(final FileTypeEvent event
) {
167 final Map
<FileType
, Set
<String
>> oldExtensions
= myTypeToExtensionMap
;
168 myTypeToExtensionMap
= null;
169 if (oldExtensions
!= null) {
170 final FileTypeManager manager
= event
.getManager();
171 final Map
<FileType
, Set
<String
>> newExtensions
= new HashMap
<FileType
, Set
<String
>>();
172 for (FileType type
: manager
.getRegisteredFileTypes()) {
173 newExtensions
.put(type
, getExtensions(manager
, type
));
175 // we are interested only in extension changes or removals.
176 // addition of an extension is handled separately by RootsChanged event
177 if (!newExtensions
.keySet().containsAll(oldExtensions
.keySet())) {
181 for (FileType type
: oldExtensions
.keySet()) {
182 if (!newExtensions
.get(type
).containsAll(oldExtensions
.get(type
))) {
190 private Set
<String
> getExtensions(FileTypeManager manager
, FileType type
) {
191 final Set
<String
> set
= new HashSet
<String
>();
192 for (FileNameMatcher matcher
: manager
.getAssociations(type
)) {
193 set
.add(matcher
.getPresentableString());
198 private void rebuildAllndices() {
199 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
200 requestRebuild(indexId
);
205 connection
.subscribe(VirtualFileManager
.VFS_CHANGES
, new BulkFileListener() {
206 public void before(List
<?
extends VFileEvent
> events
) {
207 for (VFileEvent event
: events
) {
208 if (event
.getRequestor() instanceof FileDocumentManager
) {
209 cleanupMemoryStorage();
215 public void after(List
<?
extends VFileEvent
> events
) {
219 ApplicationManager
.getApplication().addApplicationListener(new ApplicationAdapter() {
220 public void writeActionStarted(Object action
) {
221 myUpToDateIndices
.clear();
225 final File workInProgressFile
= getMarkerFile();
226 if (workInProgressFile
.exists()) {
227 // previous IDEA session was closed incorrectly, so drop all indices
228 FileUtil
.delete(PathManager
.getIndexRoot());
232 final FileBasedIndexExtension
[] extensions
= Extensions
.getExtensions(FileBasedIndexExtension
.EXTENSION_POINT_NAME
);
233 for (FileBasedIndexExtension
<?
, ?
> extension
: extensions
) {
234 myRebuildStatus
.put(extension
.getName(), new AtomicInteger(OK
));
237 final File corruptionMarker
= new File(PathManager
.getIndexRoot(), CORRUPTION_MARKER_NAME
);
238 final boolean currentVersionCorrupted
= corruptionMarker
.exists();
239 for (FileBasedIndexExtension
<?
, ?
> extension
: extensions
) {
240 registerIndexer(extension
, currentVersionCorrupted
);
242 FileUtil
.delete(corruptionMarker
);
243 dropUnregisteredIndices();
245 // check if rebuild was requested for any index during registration
246 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
247 if (myRebuildStatus
.get(indexId
).compareAndSet(REQUIRES_REBUILD
, OK
)) {
251 catch (StorageException e
) {
252 requestRebuild(indexId
);
258 myChangedFilesUpdater
= new ChangedFilesUpdater();
259 vfManager
.addVirtualFileListener(myChangedFilesUpdater
);
261 vfManager
.registerRefreshUpdater(myChangedFilesUpdater
);
263 registerIndexableSet(new AdditionalIndexableFileSet());
266 ShutDownTracker
.getInstance().registerShutdownTask(new Runnable() {
271 FileUtil
.createIfDoesntExist(workInProgressFile
);
272 saveRegisteredIndices(myIndices
.keySet());
276 private static String
calcConfigPath(final String path
) {
278 final String _path
= FileUtil
.toSystemIndependentName(new File(path
).getCanonicalPath());
279 return _path
.endsWith("/")? _path
: _path
+ "/" ;
281 catch (IOException e
) {
287 private static FileBasedIndex ourInstance
= CachedSingletonsRegistry
.markCachedField(FileBasedIndex
.class);
288 public static FileBasedIndex
getInstance() {
289 if (ourInstance
== null) {
290 ourInstance
= ApplicationManager
.getApplication().getComponent(FileBasedIndex
.class);
297 * @return true if registered index requires full rebuild for some reason, e.g. is just created or corrupted @param extension
298 * @param isCurrentVersionCorrupted
300 private <K
, V
> void registerIndexer(final FileBasedIndexExtension
<K
, V
> extension
, final boolean isCurrentVersionCorrupted
) throws IOException
{
301 final ID
<K
, V
> name
= extension
.getName();
302 final int version
= extension
.getVersion();
303 if (!extension
.dependsOnFileContent()) {
304 myNotRequiringContentIndices
.add(name
);
306 myIndexIdToVersionMap
.put(name
, version
);
307 final File versionFile
= IndexInfrastructure
.getVersionFile(name
);
308 if (isCurrentVersionCorrupted
|| IndexInfrastructure
.versionDiffers(versionFile
, version
)) {
309 if (!isCurrentVersionCorrupted
) {
310 LOG
.info("Version has changed for index " + extension
.getName() + ". The index will be rebuilt.");
312 FileUtil
.delete(IndexInfrastructure
.getIndexRootDir(name
));
313 IndexInfrastructure
.rewriteVersion(versionFile
, version
);
316 for (int attempt
= 0; attempt
< 2; attempt
++) {
318 final MapIndexStorage
<K
, V
> storage
= new MapIndexStorage
<K
, V
>(IndexInfrastructure
.getStorageFile(name
), extension
.getKeyDescriptor(), extension
.getValueExternalizer(), extension
.getCacheSize());
319 final IndexStorage
<K
, V
> memStorage
= new MemoryIndexStorage
<K
, V
>(storage
);
320 final UpdatableIndex
<K
, V
, FileContent
> index
= createIndex(name
, extension
, memStorage
);
321 myIndices
.put(name
, new Pair
<UpdatableIndex
<?
,?
, FileContent
>, InputFilter
>(index
, new IndexableFilesFilter(extension
.getInputFilter())));
322 myUnsavedDataIndexingSemaphores
.put(name
, new Semaphore());
323 myNoLimitCheckTypes
.addAll(extension
.getFileTypesWithSizeLimitNotApplicable());
326 catch (IOException e
) {
328 FileUtil
.delete(IndexInfrastructure
.getIndexRootDir(name
));
329 IndexInfrastructure
.rewriteVersion(versionFile
, version
);
334 private static void saveRegisteredIndices(Collection
<ID
<?
, ?
>> ids
) {
335 final File file
= getRegisteredIndicesFile();
337 FileUtil
.createIfDoesntExist(file
);
338 final DataOutputStream os
= new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file
)));
340 os
.writeInt(ids
.size());
341 for (ID
<?
, ?
> id
: ids
) {
342 IOUtil
.writeString(id
.toString(), os
);
349 catch (IOException ignored
) {
353 private static Set
<String
> readRegistsredIndexNames() {
354 final Set
<String
> result
= new HashSet
<String
>();
356 final DataInputStream in
= new DataInputStream(new BufferedInputStream(new FileInputStream(getRegisteredIndicesFile())));
358 final int size
= in
.readInt();
359 for (int idx
= 0; idx
< size
; idx
++) {
360 result
.add(IOUtil
.readString(in
));
367 catch (IOException ignored
) {
372 private static File
getRegisteredIndicesFile() {
373 return new File(PathManager
.getIndexRoot(), "registered");
376 private static File
getMarkerFile() {
377 return new File(PathManager
.getIndexRoot(), "work_in_progress");
380 private <K
, V
> UpdatableIndex
<K
, V
, FileContent
> createIndex(final ID
<K
, V
> indexId
, final FileBasedIndexExtension
<K
, V
> extension
, final IndexStorage
<K
, V
> storage
) throws IOException
{
381 final MapReduceIndex
<K
, V
, FileContent
> index
;
382 if (extension
instanceof CustomImplementationFileBasedIndexExtension
) {
383 final UpdatableIndex
<K
, V
, FileContent
> custom
=
384 ((CustomImplementationFileBasedIndexExtension
<K
, V
, FileContent
>)extension
).createIndexImplementation(indexId
, this, storage
);
385 if (!(custom
instanceof MapReduceIndex
)) {
388 index
= (MapReduceIndex
<K
,V
, FileContent
>)custom
;
391 index
= new MapReduceIndex
<K
, V
, FileContent
>(indexId
, extension
.getIndexer(), storage
);
394 final KeyDescriptor
<K
> keyDescriptor
= extension
.getKeyDescriptor();
395 index
.setInputIdToDataKeysIndex(new Factory
<PersistentHashMap
<Integer
, Collection
<K
>>>() {
396 public PersistentHashMap
<Integer
, Collection
<K
>> create() {
398 return createIdToDataKeysIndex(indexId
, keyDescriptor
);
400 catch (IOException e
) {
401 throw new RuntimeException(e
);
409 private static <K
> PersistentHashMap
<Integer
, Collection
<K
>> createIdToDataKeysIndex(ID
<K
, ?
> indexId
, final KeyDescriptor
<K
> keyDescriptor
) throws IOException
{
410 final File indexStorageFile
= IndexInfrastructure
.getInputIndexStorageFile(indexId
);
411 return new PersistentHashMap
<Integer
, Collection
<K
>>(indexStorageFile
, new EnumeratorIntegerDescriptor(), new DataExternalizer
<Collection
<K
>>() {
412 public void save(DataOutput out
, Collection
<K
> value
) throws IOException
{
413 DataInputOutputUtil
.writeINT(out
, value
.size());
414 for (K key
: value
) {
415 keyDescriptor
.save(out
, key
);
419 public Collection
<K
> read(DataInput in
) throws IOException
{
420 final int size
= DataInputOutputUtil
.readINT(in
);
421 final List
<K
> list
= new ArrayList
<K
>();
422 for (int idx
= 0; idx
< size
; idx
++) {
423 list
.add(keyDescriptor
.read(in
));
432 public String
getComponentName() {
433 return "FileBasedIndex";
436 public void initComponent() {
439 public void disposeComponent() {
443 private AtomicBoolean myShutdownPerformed
= new AtomicBoolean(false);
445 private void performShutdown() {
446 if (!myShutdownPerformed
.compareAndSet(false, true)) {
447 return; // already shut down
449 myFileDocumentManager
.saveAllDocuments();
451 LOG
.info("START INDEX SHUTDOWN");
453 myChangedFilesUpdater
.forceUpdate(null);
455 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
456 final UpdatableIndex
<?
, ?
, FileContent
> index
= getIndex(indexId
);
457 assert index
!= null;
458 checkRebuild(indexId
, true); // if the index was scheduled for rebuild, only clean it
459 //LOG.info("DISPOSING " + indexId);
463 myVfManager
.removeVirtualFileListener(myChangedFilesUpdater
);
464 myVfManager
.unregisterRefreshUpdater(myChangedFilesUpdater
);
466 FileUtil
.delete(getMarkerFile());
468 catch (Throwable e
) {
469 LOG
.info("Problems during index shutdown", e
);
470 throw new RuntimeException(e
);
472 LOG
.info("END INDEX SHUTDOWN");
475 public void flushCaches() {
476 IndexingStamp
.flushCache();
477 ApplicationManager
.getApplication().executeOnPooledThread(new Runnable() {
479 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
480 //noinspection ConstantConditions
482 getIndex(indexId
).flush();
484 catch (StorageException e
) {
486 requestRebuild(indexId
);
494 * @param project it is guaranteeed to return data which is up-to-date withing the project
495 * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist
498 public <K
> Collection
<K
> getAllKeys(final ID
<K
, ?
> indexId
, @NotNull Project project
) {
499 Set
<K
> allKeys
= new HashSet
<K
>();
500 processAllKeys(indexId
, new CommonProcessors
.CollectProcessor
<K
>(allKeys
), project
);
505 * @param project it is guaranteeed to return data which is up-to-date withing the project
506 * Keys obtained from the files which do not belong to the project specified may not be up-to-date or even exist
508 public <K
> boolean processAllKeys(final ID
<K
, ?
> indexId
, Processor
<K
> processor
, @NotNull Project project
) {
510 ensureUpToDate(indexId
, project
, GlobalSearchScope
.allScope(project
));
511 final UpdatableIndex
<K
, ?
, FileContent
> index
= getIndex(indexId
);
512 if (index
== null) return true;
513 return index
.processAllKeys(processor
);
515 catch (StorageException e
) {
516 scheduleRebuild(indexId
, e
);
518 catch (RuntimeException e
) {
519 final Throwable cause
= e
.getCause();
520 if (cause
instanceof StorageException
|| cause
instanceof IOException
) {
521 scheduleRebuild(indexId
, cause
);
531 private static final ThreadLocal
<Integer
> myUpToDateCheckState
= new ThreadLocal
<Integer
>();
533 public void disableUpToDateCheckForCurrentThread() {
534 final Integer currentValue
= myUpToDateCheckState
.get();
535 myUpToDateCheckState
.set(currentValue
== null?
1 : currentValue
.intValue() + 1);
538 public void enableUpToDateCheckForCurrentThread() {
539 final Integer currentValue
= myUpToDateCheckState
.get();
540 if (currentValue
!= null) {
541 final int newValue
= currentValue
.intValue() - 1;
543 myUpToDateCheckState
.set(newValue
);
546 myUpToDateCheckState
.remove();
551 private boolean isUpToDateCheckEnabled() {
552 final Integer value
= myUpToDateCheckState
.get();
553 return value
== null || value
.intValue() == 0;
557 private final ThreadLocal
<Boolean
> myReentrancyGuard
= new ThreadLocal
<Boolean
>() {
558 protected Boolean
initialValue() {
559 return Boolean
.FALSE
;
564 * DO NOT CALL DIRECTLY IN CLIENT CODE
565 * The method is internal to indexing engine end is called internally. The method is public due to implementation details
567 public <K
> void ensureUpToDate(final ID
<K
, ?
> indexId
, @NotNull Project project
, @Nullable GlobalSearchScope filter
) {
568 if (isDumb(project
)) {
569 handleDumbMode(indexId
, project
);
572 if (myReentrancyGuard
.get().booleanValue()) {
573 //assert false : "ensureUpToDate() is not reentrant!";
576 myReentrancyGuard
.set(Boolean
.TRUE
);
579 myChangedFilesUpdater
.ensureAllInvalidateTasksCompleted();
580 if (isUpToDateCheckEnabled()) {
582 checkRebuild(indexId
, false);
583 myChangedFilesUpdater
.forceUpdate(filter
);
584 indexUnsavedDocuments(indexId
, project
, filter
);
586 catch (StorageException e
) {
587 scheduleRebuild(indexId
, e
);
589 catch (RuntimeException e
) {
590 final Throwable cause
= e
.getCause();
591 if (cause
instanceof StorageException
|| cause
instanceof IOException
) {
592 scheduleRebuild(indexId
, e
);
601 myReentrancyGuard
.set(Boolean
.FALSE
);
605 private void handleDumbMode(final ID
<?
, ?
> indexId
, Project project
) {
606 if (myNotRequiringContentIndices
.contains(indexId
)) {
607 return; //indexed eagerly in foreground while building unindexed file list
610 ProgressManager
.checkCanceled(); // DumbModeAction.CANCEL
612 final ProgressIndicator progressIndicator
= ProgressManager
.getInstance().getProgressIndicator();
613 if (progressIndicator
instanceof BackgroundableProcessIndicator
) {
614 final BackgroundableProcessIndicator indicator
= (BackgroundableProcessIndicator
)progressIndicator
;
615 if (indicator
.getDumbModeAction() == DumbModeAction
.WAIT
) {
616 assert !ApplicationManager
.getApplication().isDispatchThread();
617 DumbService
.getInstance(project
).waitForSmartMode();
622 throw new IndexNotReadyException();
625 private static boolean isDumb(Project project
) {
626 return DumbServiceImpl
.getInstance(project
).isDumb();
630 public <K
, V
> List
<V
> getValues(final ID
<K
, V
> indexId
, @NotNull K dataKey
, @NotNull final GlobalSearchScope filter
) {
631 final List
<V
> values
= new ArrayList
<V
>();
632 processValuesImpl(indexId
, dataKey
, true, null, new ValueProcessor
<V
>() {
633 public boolean process(final VirtualFile file
, final V value
) {
642 public <K
, V
> Collection
<VirtualFile
> getContainingFiles(final ID
<K
, V
> indexId
, @NotNull K dataKey
, @NotNull final GlobalSearchScope filter
) {
643 final Set
<VirtualFile
> files
= new HashSet
<VirtualFile
>();
644 processValuesImpl(indexId
, dataKey
, false, null, new ValueProcessor
<V
>() {
645 public boolean process(final VirtualFile file
, final V value
) {
654 public interface ValueProcessor
<V
> {
656 * @param value a value to process
657 * @param file the file the value came from
658 * @return false if no further processing is needed, true otherwise
660 boolean process(VirtualFile file
, V value
);
664 * @return false if ValueProcessor.process() returned false; true otherwise or if ValueProcessor was not called at all
666 public <K
, V
> boolean processValues(final ID
<K
, V
> indexId
, final @NotNull K dataKey
, @Nullable final VirtualFile inFile
,
667 ValueProcessor
<V
> processor
, @NotNull final GlobalSearchScope filter
) {
668 return processValuesImpl(indexId
, dataKey
, false, inFile
, processor
, filter
);
673 private <K
, V
> boolean processValuesImpl(final ID
<K
, V
> indexId
, final K dataKey
, boolean ensureValueProcessedOnce
,
674 @Nullable final VirtualFile restrictToFile
, ValueProcessor
<V
> processor
,
675 final GlobalSearchScope filter
) {
677 final Project project
= filter
.getProject();
678 assert project
!= null : "GlobalSearchScope#getProject() should be not-null for all index queries";
679 ensureUpToDate(indexId
, project
, filter
);
680 final UpdatableIndex
<K
, V
, FileContent
> index
= getIndex(indexId
);
685 final Lock readLock
= index
.getReadLock();
688 final ValueContainer
<V
> container
= index
.getData(dataKey
);
690 boolean shouldContinue
= true;
692 if (restrictToFile
!= null) {
693 if (restrictToFile
instanceof VirtualFileWithId
) {
694 final int restrictedFileId
= getFileId(restrictToFile
);
695 for (final Iterator
<V
> valueIt
= container
.getValueIterator(); valueIt
.hasNext();) {
696 final V value
= valueIt
.next();
697 if (container
.isAssociated(value
, restrictedFileId
)) {
698 shouldContinue
= processor
.process(restrictToFile
, value
);
699 if (!shouldContinue
) {
707 final PersistentFS fs
= (PersistentFS
)ManagingFS
.getInstance();
708 VALUES_LOOP
: for (final Iterator
<V
> valueIt
= container
.getValueIterator(); valueIt
.hasNext();) {
709 final V value
= valueIt
.next();
710 for (final ValueContainer
.IntIterator inputIdsIterator
= container
.getInputIdsIterator(value
); inputIdsIterator
.hasNext();) {
711 final int id
= inputIdsIterator
.next();
712 VirtualFile file
= IndexInfrastructure
.findFileById(fs
, id
);
713 if (file
!= null && filter
.accept(file
)) {
714 shouldContinue
= processor
.process(file
, value
);
715 if (!shouldContinue
) {
718 if (ensureValueProcessedOnce
) {
719 break; // continue with the next value
725 return shouldContinue
;
728 index
.getReadLock().unlock();
731 catch (StorageException e
) {
732 scheduleRebuild(indexId
, e
);
734 catch (RuntimeException e
) {
735 final Throwable cause
= e
.getCause();
736 if (cause
instanceof StorageException
|| cause
instanceof IOException
) {
737 scheduleRebuild(indexId
, cause
);
746 public <K
, V
> boolean getFilesWithKey(final ID
<K
, V
> indexId
, final Set
<K
> dataKeys
,
747 Processor
<VirtualFile
> processor
,
748 GlobalSearchScope filter
) {
750 final Project project
= filter
.getProject();
751 assert project
!= null : "GlobalSearchScope#getProject() should be not-null for all index queries";
752 ensureUpToDate(indexId
, project
, filter
);
753 final UpdatableIndex
<K
, V
, FileContent
> index
= getIndex(indexId
);
758 final Lock readLock
= index
.getReadLock();
761 List
<TIntHashSet
> locals
= new ArrayList
<TIntHashSet
>();
762 for (K dataKey
: dataKeys
) {
763 TIntHashSet local
= new TIntHashSet();
765 final ValueContainer
<V
> container
= index
.getData(dataKey
);
767 for (final Iterator
<V
> valueIt
= container
.getValueIterator(); valueIt
.hasNext();) {
768 final V value
= valueIt
.next();
769 for (final ValueContainer
.IntIterator inputIdsIterator
= container
.getInputIdsIterator(value
); inputIdsIterator
.hasNext();) {
770 final int id
= inputIdsIterator
.next();
776 if (locals
.size() == 0) return true;
778 Collections
.sort(locals
, new Comparator
<TIntHashSet
>() {
779 public int compare(TIntHashSet o1
, TIntHashSet o2
) {
780 return o1
.size() - o2
.size();
784 final PersistentFS fs
= (PersistentFS
)ManagingFS
.getInstance();
785 TIntIterator ids
= join(locals
).iterator();
786 while (ids
.hasNext()) {
788 VirtualFile file
= IndexInfrastructure
.findFileById(fs
, id
);
789 if (file
!= null && filter
.accept(file
)) {
790 if (!processor
.process(file
)) return false;
795 index
.getReadLock().unlock();
798 catch (StorageException e
) {
799 scheduleRebuild(indexId
, e
);
801 catch (RuntimeException e
) {
802 final Throwable cause
= e
.getCause();
803 if (cause
instanceof StorageException
|| cause
instanceof IOException
) {
804 scheduleRebuild(indexId
, cause
);
813 private static TIntHashSet
join(List
<TIntHashSet
> locals
) {
814 TIntHashSet result
= locals
.get(0);
815 if (locals
.size() > 1) {
816 TIntIterator it
= result
.iterator();
818 while (it
.hasNext()) {
820 for (int i
= 1; i
< locals
.size(); i
++) {
821 if (!locals
.get(i
).contains(id
)) {
831 public interface AllValuesProcessor
<V
> {
832 void process(final int inputId
, V value
);
835 public <K
, V
> void processAllValues(final ID
<K
, V
> indexId
, AllValuesProcessor
<V
> processor
, @NotNull Project project
) {
837 ensureUpToDate(indexId
, project
, null);
838 final UpdatableIndex
<K
, V
, FileContent
> index
= getIndex(indexId
);
843 index
.getReadLock().lock();
844 for (K dataKey
: index
.getAllKeys()) {
845 final ValueContainer
<V
> container
= index
.getData(dataKey
);
846 for (final Iterator
<V
> it
= container
.getValueIterator(); it
.hasNext();) {
847 final V value
= it
.next();
848 for (final ValueContainer
.IntIterator inputsIt
= container
.getInputIdsIterator(value
); inputsIt
.hasNext();) {
849 processor
.process(inputsIt
.next(), value
);
855 index
.getReadLock().unlock();
858 catch (StorageException e
) {
859 scheduleRebuild(indexId
, e
);
861 catch (RuntimeException e
) {
862 final Throwable cause
= e
.getCause();
863 if (cause
instanceof StorageException
|| cause
instanceof IOException
) {
864 scheduleRebuild(indexId
, e
);
872 private <K
> void scheduleRebuild(final ID
<K
, ?
> indexId
, final Throwable e
) {
873 requestRebuild(indexId
);
875 checkRebuild(indexId
, false);
879 public boolean isIndexReady(final ID
<?
, ?
> indexId
) {
880 return myRebuildStatus
.get(indexId
).get() == OK
;
883 private void checkRebuild(final ID
<?
, ?
> indexId
, final boolean cleanupOnly
) {
884 if (myRebuildStatus
.get(indexId
).compareAndSet(REQUIRES_REBUILD
, REBUILD_IN_PROGRESS
)) {
885 cleanupProcessedFlag();
887 final Runnable rebuildRunnable
= new Runnable() {
892 final FileSystemSynchronizerImpl synchronizer
= new FileSystemSynchronizerImpl();
893 synchronizer
.setCancelable(false);
894 for (Project project
: ProjectManager
.getInstance().getOpenProjects()) {
895 synchronizer
.registerCacheUpdater(new UnindexedFilesUpdater(project
, ProjectRootManager
.getInstance(project
), FileBasedIndex
.this));
897 synchronizer
.executeFileUpdate();
900 catch (StorageException e
) {
901 requestRebuild(indexId
);
905 myRebuildStatus
.get(indexId
).compareAndSet(REBUILD_IN_PROGRESS
, OK
);
910 final Application application
= ApplicationManager
.getApplication();
911 if (cleanupOnly
|| application
.isUnitTestMode()) {
912 rebuildRunnable
.run();
915 SwingUtilities
.invokeLater(new Runnable() {
917 new Task
.Modal(null, "Updating index", false) {
918 public void run(@NotNull final ProgressIndicator indicator
) {
919 indicator
.setIndeterminate(true);
920 rebuildRunnable
.run();
928 if (myRebuildStatus
.get(indexId
).get() == REBUILD_IN_PROGRESS
) {
929 throw new ProcessCanceledException();
933 private void clearIndex(final ID
<?
, ?
> indexId
) throws StorageException
{
934 final UpdatableIndex
<?
, ?
, FileContent
> index
= getIndex(indexId
);
935 assert index
!= null;
938 IndexInfrastructure
.rewriteVersion(IndexInfrastructure
.getVersionFile(indexId
), myIndexIdToVersionMap
.get(indexId
));
940 catch (IOException e
) {
945 private Set
<Document
> getUnsavedOrTransactedDocuments() {
946 Set
<Document
> docs
= new HashSet
<Document
>(Arrays
.asList(myFileDocumentManager
.getUnsavedDocuments()));
947 docs
.addAll(myTransactionMap
.keySet());
951 private void indexUnsavedDocuments(ID
<?
, ?
> indexId
, Project project
, GlobalSearchScope filter
) throws StorageException
{
953 if (myUpToDateIndices
.contains(indexId
)) {
954 return; // no need to index unsaved docs
957 final Set
<Document
> documents
= getUnsavedOrTransactedDocuments();
958 if (!documents
.isEmpty()) {
959 // now index unsaved data
960 final StorageGuard
.Holder guard
= setDataBufferingEnabled(true);
962 boolean allDocsProcessed
= true;
963 final Semaphore semaphore
= myUnsavedDataIndexingSemaphores
.get(indexId
);
966 for (Document document
: documents
) {
967 allDocsProcessed
&= indexUnsavedDocument(document
, indexId
, project
, filter
);
973 while (!semaphore
.waitFor(500)) { // may need to wait until another thread is done with indexing
974 if (Thread
.holdsLock(PsiLock
.LOCK
)) {
975 break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
978 if (allDocsProcessed
) {
979 myUpToDateIndices
.add(indexId
); // safe to set the flag here, becase it will be cleared under the WriteAction
989 private interface DocumentContent
{
991 long getModificationStamp();
994 private static class AuthenticContent
implements DocumentContent
{
995 private final Document myDocument
;
997 private AuthenticContent(final Document document
) {
998 myDocument
= document
;
1001 public String
getText() {
1002 return myDocument
.getText();
1005 public long getModificationStamp() {
1006 return myDocument
.getModificationStamp();
1010 private static class PsiContent
implements DocumentContent
{
1011 private final Document myDocument
;
1012 private final PsiFile myFile
;
1014 private PsiContent(final Document document
, final PsiFile file
) {
1015 myDocument
= document
;
1019 public String
getText() {
1020 if (myFile
.getModificationStamp() != myDocument
.getModificationStamp()) {
1021 final ASTNode node
= myFile
.getNode();
1022 assert node
!= null;
1023 return node
.getText();
1025 return myDocument
.getText();
1028 public long getModificationStamp() {
1029 return myFile
.getModificationStamp();
1033 // returns false if doc was not indexed because the file does not fit in scope
1034 private boolean indexUnsavedDocument(final Document document
, final ID
<?
, ?
> requestedIndexId
, Project project
, GlobalSearchScope filter
) throws StorageException
{
1035 final VirtualFile vFile
= myFileDocumentManager
.getFile(document
);
1036 if (!(vFile
instanceof VirtualFileWithId
) || !vFile
.isValid()) {
1039 if (filter
!= null && !filter
.accept(vFile
)) {
1042 final PsiFile dominantContentFile
= findDominantPsiForDocument(document
, project
);
1044 DocumentContent content
;
1045 if (dominantContentFile
!= null && dominantContentFile
.getModificationStamp() != document
.getModificationStamp()) {
1046 content
= new PsiContent(document
, dominantContentFile
);
1049 content
= new AuthenticContent(document
);
1052 final long currentDocStamp
= content
.getModificationStamp();
1053 if (currentDocStamp
!= myLastIndexedDocStamps
.getAndSet(document
, requestedIndexId
, currentDocStamp
).longValue()) {
1054 final FileContent newFc
= new FileContent(vFile
, content
.getText(), vFile
.getCharset());
1056 if (dominantContentFile
!= null) {
1057 dominantContentFile
.putUserData(PsiFileImpl
.BUILDING_STUB
, true);
1058 newFc
.putUserData(PSI_FILE
, dominantContentFile
);
1061 if (content
instanceof AuthenticContent
) {
1062 newFc
.putUserData(EDITOR_HIGHLIGHTER
, document
instanceof DocumentImpl
1063 ?
((DocumentImpl
)document
).getEditorHighlighterForCachesBuilding() : null);
1066 if (getInputFilter(requestedIndexId
).acceptInput(vFile
)) {
1067 newFc
.putUserData(PROJECT
, project
);
1068 final int inputId
= Math
.abs(getFileId(vFile
));
1069 getIndex(requestedIndexId
).update(inputId
, newFc
);
1072 if (dominantContentFile
!= null) {
1073 dominantContentFile
.putUserData(PsiFileImpl
.BUILDING_STUB
, null);
1079 public static final Key
<PsiFile
> PSI_FILE
= new Key
<PsiFile
>("PSI for stubs");
1080 public static final Key
<EditorHighlighter
> EDITOR_HIGHLIGHTER
= new Key
<EditorHighlighter
>("Editor");
1081 public static final Key
<Project
> PROJECT
= new Key
<Project
>("Context project");
1082 public static final Key
<VirtualFile
> VIRTUAL_FILE
= new Key
<VirtualFile
>("Context virtual file");
1085 private PsiFile
findDominantPsiForDocument(final Document document
, Project project
) {
1086 if (myTransactionMap
.containsKey(document
)) {
1087 return myTransactionMap
.get(document
);
1090 return findLatestKnownPsiForUncomittedDocument(document
, project
);
1093 private final StorageGuard myStorageLock
= new StorageGuard();
1095 private StorageGuard
.Holder
setDataBufferingEnabled(final boolean enabled
) {
1096 final StorageGuard
.Holder holder
= myStorageLock
.enter(enabled
);
1097 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
1098 final MapReduceIndex index
= (MapReduceIndex
)getIndex(indexId
);
1099 assert index
!= null;
1100 final IndexStorage indexStorage
= index
.getStorage();
1101 ((MemoryIndexStorage
)indexStorage
).setBufferingEnabled(enabled
);
1106 private void cleanupMemoryStorage() {
1107 synchronized (myLastIndexedDocStamps
) {
1108 myLastIndexedDocStamps
.clear();
1110 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
1111 final MapReduceIndex index
= (MapReduceIndex
)getIndex(indexId
);
1112 assert index
!= null;
1113 final MemoryIndexStorage memStorage
= (MemoryIndexStorage
)index
.getStorage();
1114 index
.getWriteLock().lock();
1116 memStorage
.clearMemoryMap();
1119 index
.getWriteLock().unlock();
1121 memStorage
.fireMemoryStorageCleared();
1125 private void dropUnregisteredIndices() {
1126 final Set
<String
> indicesToDrop
= readRegistsredIndexNames();
1127 for (ID
<?
, ?
> key
: myIndices
.keySet()) {
1128 indicesToDrop
.remove(key
.toString());
1130 for (String s
: indicesToDrop
) {
1131 FileUtil
.delete(IndexInfrastructure
.getIndexRootDir(ID
.create(s
)));
1135 public void requestRebuild(ID
<?
, ?
> indexId
) {
1136 requestRebuild(indexId
, new Throwable());
1139 public void requestRebuild(ID
<?
, ?
> indexId
, Throwable throwable
) {
1140 cleanupProcessedFlag();
1141 LOG
.info("Rebuild requested for index " + indexId
, throwable
);
1142 myRebuildStatus
.get(indexId
).set(REQUIRES_REBUILD
);
1145 private <K
, V
> UpdatableIndex
<K
, V
, FileContent
> getIndex(ID
<K
, V
> indexId
) {
1146 final Pair
<UpdatableIndex
<?
, ?
, FileContent
>, InputFilter
> pair
= myIndices
.get(indexId
);
1147 //noinspection unchecked
1148 return pair
!= null?
(UpdatableIndex
<K
,V
, FileContent
>)pair
.getFirst() : null;
1151 private InputFilter
getInputFilter(ID
<?
, ?
> indexId
) {
1152 final Pair
<UpdatableIndex
<?
, ?
, FileContent
>, InputFilter
> pair
= myIndices
.get(indexId
);
1153 return pair
!= null? pair
.getSecond() : null;
1156 public void indexFileContent(com
.intellij
.ide
.startup
.FileContent content
) {
1157 myChangedFilesUpdater
.ensureAllInvalidateTasksCompleted();
1158 final VirtualFile file
= content
.getVirtualFile();
1159 FileContent fc
= null;
1161 PsiFile psiFile
= null;
1163 final Ref
<ProcessCanceledException
> pce
= Ref
.create(null);
1165 final List
<Runnable
> tasks
= new ArrayList
<Runnable
>();
1166 for (final ID
<?
, ?
> indexId
: myIndices
.keySet()) {
1167 if (shouldIndexFile(file
, indexId
)) {
1169 byte[] currentBytes
;
1171 currentBytes
= content
.getBytes();
1173 catch (IOException e
) {
1174 currentBytes
= ArrayUtil
.EMPTY_BYTE_ARRAY
;
1176 fc
= new FileContent(file
, currentBytes
);
1178 psiFile
= content
.getUserData(PSI_FILE
);
1179 if (psiFile
!= null) {
1180 psiFile
.putUserData(PsiFileImpl
.BUILDING_STUB
, true);
1181 fc
.putUserData(PSI_FILE
, psiFile
);
1183 Project project
= content
.getUserData(PROJECT
);
1184 if (project
== null) {
1185 project
= ProjectUtil
.guessProjectForFile(file
);
1187 fc
.putUserData(PROJECT
, project
);
1190 final FileContent _fc
= fc
;
1191 tasks
.add(new Runnable() {
1194 updateSingleIndex(indexId
, file
, _fc
);
1196 catch (ProcessCanceledException e
) {
1199 catch (StorageException e
) {
1200 requestRebuild(indexId
);
1208 if (tasks
.size() > 0) {
1209 if (Registry
.get(USE_MULTITHREADED_INDEXING
).asBoolean()) {
1210 final Job
<Object
> job
= JobScheduler
.getInstance().createJob("IndexJob", Job
.DEFAULT_PRIORITY
/ 2);
1212 for (Runnable task
: tasks
) {
1215 job
.scheduleAndWaitForResults();
1217 catch (Throwable throwable
) {
1218 LOG
.info(throwable
);
1222 for (Runnable task
: tasks
) {
1228 if (!pce
.isNull()) {
1229 myChangedFilesUpdater
.scheduleForUpdate(file
);
1233 if (psiFile
!= null) {
1234 psiFile
.putUserData(PsiFileImpl
.BUILDING_STUB
, null);
1238 private void updateSingleIndex(final ID
<?
, ?
> indexId
, final VirtualFile file
, final FileContent currentFC
)
1239 throws StorageException
{
1240 if (myRebuildStatus
.get(indexId
).get() == REQUIRES_REBUILD
) {
1241 return; // the index is scheduled for rebuild, no need to update
1244 final StorageGuard
.Holder lock
= setDataBufferingEnabled(false);
1247 final int inputId
= Math
.abs(getFileId(file
));
1249 final UpdatableIndex
<?
, ?
, FileContent
> index
= getIndex(indexId
);
1250 assert index
!= null;
1252 index
.update(inputId
, currentFC
);
1253 ApplicationManager
.getApplication().runReadAction(new Runnable() {
1255 if (file
.isValid()) {
1256 if (currentFC
!= null) {
1257 IndexingStamp
.update(file
, indexId
, IndexInfrastructure
.getIndexCreationStamp(indexId
));
1260 // mark the file as unindexed
1261 IndexingStamp
.update(file
, indexId
, -1L);
1272 public static int getFileId(final VirtualFile file
) {
1273 if (file
instanceof VirtualFileWithId
) {
1274 return ((VirtualFileWithId
)file
).getId();
1277 throw new IllegalArgumentException("Virtual file doesn't support id: " + file
+ ", implementation class: " + file
.getClass().getName());
1280 private boolean needsFileContentLoading(ID
<?
, ?
> indexId
) {
1281 return !myNotRequiringContentIndices
.contains(indexId
);
1284 private abstract static class InvalidationTask
implements Runnable
{
1285 private final VirtualFile mySubj
;
1287 protected InvalidationTask(final VirtualFile subj
) {
1291 public VirtualFile
getSubj() {
1296 private final class ChangedFilesUpdater
extends VirtualFileAdapter
implements BackgroundableCacheUpdater
{
1297 private final Set
<VirtualFile
> myFilesToUpdate
= new LinkedHashSet
<VirtualFile
>();
1298 private final Queue
<InvalidationTask
> myFutureInvalidations
= new LinkedList
<InvalidationTask
>();
1299 private final JBReentrantReadWriteLock myLock
= LockFactory
.createReadWriteLock();
1300 private final JBLock r
= myLock
.readLock();
1301 private final JBLock w
= myLock
.writeLock();
1303 private final ManagingFS myManagingFS
= ManagingFS
.getInstance();
1304 // No need to react on movement events since files stay valid, their ids don't change and all associated attributes remain intact.
1306 public void fileCreated(final VirtualFileEvent event
) {
1310 public void fileDeleted(final VirtualFileEvent event
) {
1313 myFilesToUpdate
.remove(event
.getFile()); // no need to update it anymore
1320 public void fileCopied(final VirtualFileCopyEvent event
) {
1324 public void beforeFileDeletion(final VirtualFileEvent event
) {
1325 invalidateIndices(event
.getFile(), false);
1328 public void beforeContentsChange(final VirtualFileEvent event
) {
1329 invalidateIndices(event
.getFile(), true);
1332 public void contentsChanged(final VirtualFileEvent event
) {
1336 public void beforePropertyChange(final VirtualFilePropertyEvent event
) {
1337 if (event
.getPropertyName().equals(VirtualFile
.PROP_NAME
)) {
1338 // indexes may depend on file name
1339 final VirtualFile file
= event
.getFile();
1340 if (!file
.isDirectory()) {
1341 // name change may lead to filetype change so the file might become not indexable
1342 // in general case have to 'unindex' the file and index it again if needed after the name has been changed
1343 invalidateIndices(file
, false);
1348 public void propertyChanged(final VirtualFilePropertyEvent event
) {
1349 if (event
.getPropertyName().equals(VirtualFile
.PROP_NAME
)) {
1350 // indexes may depend on file name
1351 if (!event
.getFile().isDirectory()) {
1357 private void markDirty(final VirtualFileEvent event
) {
1358 cleanProcessedFlag(event
.getFile());
1359 iterateIndexableFiles(event
.getFile(), new Processor
<VirtualFile
>() {
1360 public boolean process(final VirtualFile file
) {
1361 FileContent fileContent
= null;
1362 // handle 'content-less' indices separately
1363 for (ID
<?
, ?
> indexId
: myNotRequiringContentIndices
) {
1364 if (getInputFilter(indexId
).acceptInput(file
)) {
1366 if (fileContent
== null) {
1367 fileContent
= new FileContent(file
);
1369 updateSingleIndex(indexId
, file
, fileContent
);
1371 catch (StorageException e
) {
1373 requestRebuild(indexId
);
1377 // For 'normal indices' schedule the file for update and stop iteration if at least one index accepts it
1378 if (!isTooLarge(file
)) {
1379 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
1380 if (needsFileContentLoading(indexId
) && getInputFilter(indexId
).acceptInput(file
)) {
1381 scheduleForUpdate(file
);
1382 break; // no need to iterate further, as the file is already marked
1390 IndexingStamp
.flushCache();
1393 public void scheduleForUpdate(VirtualFile file
) {
1396 myFilesToUpdate
.add(file
);
1403 void invalidateIndices(final VirtualFile file
, final boolean markForReindex
) {
1404 if (isUnderConfigOrSystem(file
)) {
1407 if (file
.isDirectory()) {
1408 if (isMock(file
) || myManagingFS
.wereChildrenAccessed(file
)) {
1409 final Collection
<VirtualFile
> children
= (file
instanceof NewVirtualFile
)?
((NewVirtualFile
)file
).getInDbChildren() : Arrays
.asList(file
.getChildren());
1410 for (VirtualFile child
: children
) {
1411 if (NullVirtualFile
.INSTANCE
!= child
) {
1412 invalidateIndices(child
, markForReindex
);
1418 cleanProcessedFlag(file
);
1419 IndexingStamp
.flushCache();
1420 final List
<ID
<?
, ?
>> affectedIndices
= new ArrayList
<ID
<?
, ?
>>(myIndices
.size());
1422 final boolean isTooLarge
= isTooLarge(file
);
1423 for (final ID
<?
, ?
> indexId
: myIndices
.keySet()) {
1425 if (myNotRequiringContentIndices
.contains(indexId
)) {
1426 if (shouldUpdateIndex(file
, indexId
)) {
1427 updateSingleIndex(indexId
, file
, null);
1430 else { // the index requires file content
1431 if (!isTooLarge
&& shouldUpdateIndex(file
, indexId
)) {
1432 affectedIndices
.add(indexId
);
1436 catch (StorageException e
) {
1438 requestRebuild(indexId
);
1442 if (affectedIndices
.size() > 0) {
1443 if (markForReindex
) {
1444 // only mark the file as unindexed, reindex will be done lazily
1445 ApplicationManager
.getApplication().runReadAction(new Runnable() {
1447 for (ID
<?
, ?
> indexId
: affectedIndices
) {
1448 IndexingStamp
.update(file
, indexId
, -1L);
1452 iterateIndexableFiles(file
, new Processor
<VirtualFile
>() {
1453 public boolean process(final VirtualFile file
) {
1454 scheduleForUpdate(file
);
1460 final InvalidationTask invalidator
= new InvalidationTask(file
) {
1462 Throwable unexpectedError
= null;
1463 for (ID
<?
, ?
> indexId
: affectedIndices
) {
1465 updateSingleIndex(indexId
, file
, null);
1467 catch (StorageException e
) {
1469 requestRebuild(indexId
);
1471 catch (ProcessCanceledException ignored
) {
1473 catch (Throwable e
) {
1475 if (unexpectedError
== null) {
1476 unexpectedError
= e
;
1480 IndexingStamp
.flushCache();
1481 if (unexpectedError
!= null) {
1482 LOG
.error(unexpectedError
);
1489 myFutureInvalidations
.offer(invalidator
);
1496 IndexingStamp
.flushCache();
1500 public void ensureAllInvalidateTasksCompleted() {
1504 size
= myFutureInvalidations
.size();
1505 if (size
== 0) return;
1510 final ProgressIndicator current
= ProgressManager
.getInstance().getProgressIndicator();
1511 final ProgressIndicator indicator
= current
!= null ? current
: new EmptyProgressIndicator();
1512 indicator
.setText("");
1515 InvalidationTask task
;
1518 task
= myFutureInvalidations
.poll();
1527 indicator
.setFraction(((double)count
++)/size
);
1528 indicator
.setText2(task
.getSubj().getPresentableUrl());
1533 private void iterateIndexableFiles(final VirtualFile file
, final Processor
<VirtualFile
> processor
) {
1534 if (file
.isDirectory()) {
1535 final ContentIterator iterator
= new ContentIterator() {
1536 public boolean processFile(final VirtualFile fileOrDir
) {
1537 if (!fileOrDir
.isDirectory()) {
1538 processor
.process(fileOrDir
);
1544 for (IndexableFileSet set
: myIndexableSets
) {
1545 set
.iterateIndexableFilesIn(file
, iterator
);
1549 for (IndexableFileSet set
: myIndexableSets
) {
1550 if (set
.isInSet(file
)) {
1551 processor
.process(file
);
1558 public boolean initiallyBackgrounded() {
1559 if (ApplicationManager
.getApplication().isCommandLine() || ApplicationManager
.getApplication().isUnitTestMode()) {
1562 return Registry
.get(DumbServiceImpl
.FILE_INDEX_BACKGROUND
).asBoolean();
1565 public boolean canBeSentToBackground(Collection
<VirtualFile
> remaining
) {
1569 public void backgrounded(Collection
<VirtualFile
> remaining
) {
1570 new BackgroundCacheUpdaterRunner(remaining
).processFiles(this);
1573 public VirtualFile
[] queryNeededFiles() {
1576 if (myFilesToUpdate
.isEmpty()) {
1577 return VirtualFile
.EMPTY_ARRAY
;
1579 return myFilesToUpdate
.toArray(new VirtualFile
[myFilesToUpdate
.size()]);
1586 public void processFile(final com
.intellij
.ide
.startup
.FileContent fileContent
) {
1587 myChangedFilesUpdater
.ensureAllInvalidateTasksCompleted();
1588 processFileImpl(fileContent
);
1591 private final Semaphore myForceUpdateSemaphore
= new Semaphore();
1593 public void forceUpdate(@Nullable GlobalSearchScope filter
) {
1594 myChangedFilesUpdater
.ensureAllInvalidateTasksCompleted();
1595 final VirtualFile
[] files
= queryNeededFiles();
1596 if (files
.length
> 0) {
1597 for (VirtualFile file
: files
) {
1598 if (filter
== null || filter
.accept(file
)) {
1600 myForceUpdateSemaphore
.down();
1601 // process only files that can affect result
1602 processFileImpl(new com
.intellij
.ide
.startup
.FileContent(file
));
1605 myForceUpdateSemaphore
.up();
1611 // If several threads entered the method at the same time and there were files to update,
1612 // all the threads should leave the method synchronously after all the files scheduled for update are reindexed,
1613 // no matter which thread will do reindexing job.
1614 // Thus we ensure that all the threads that entered the method will get the most recent data
1616 while (!myForceUpdateSemaphore
.waitFor(500)) { // may need to wait until another thread is done with indexing
1617 if (Thread
.holdsLock(PsiLock
.LOCK
)) {
1618 break; // hack. Most probably that other indexing threads is waiting for PsiLock, which we're are holding.
1623 public void updatingDone() {
1626 public void canceled() {
1629 private void processFileImpl(final com
.intellij
.ide
.startup
.FileContent fileContent
) {
1630 final VirtualFile file
= fileContent
.getVirtualFile();
1631 final boolean reallyRemoved
;
1634 reallyRemoved
= myFilesToUpdate
.remove(file
);
1639 if (reallyRemoved
&& file
.isValid()) {
1640 indexFileContent(fileContent
);
1641 IndexingStamp
.flushCache();
1646 private class UnindexedFilesFinder
implements CollectingContentIterator
{
1647 private final List
<VirtualFile
> myFiles
= new ArrayList
<VirtualFile
>();
1648 private final ProgressIndicator myProgressIndicator
;
1650 private UnindexedFilesFinder() {
1651 myProgressIndicator
= ProgressManager
.getInstance().getProgressIndicator();
1654 public List
<VirtualFile
> getFiles() {
1658 public boolean processFile(final VirtualFile file
) {
1659 if (!file
.isDirectory()) {
1660 if (file
instanceof NewVirtualFile
&& ((NewVirtualFile
)file
).getFlag(ALREADY_PROCESSED
)) {
1664 if (file
instanceof VirtualFileWithId
) {
1665 boolean oldStuff
= true;
1666 if (!isTooLarge(file
)) {
1667 for (ID
<?
, ?
> indexId
: myIndices
.keySet()) {
1669 if (shouldIndexFile(file
, indexId
)) {
1675 catch (RuntimeException e
) {
1676 final Throwable cause
= e
.getCause();
1677 if (cause
instanceof IOException
|| cause
instanceof StorageException
) {
1679 requestRebuild(indexId
);
1687 FileContent fileContent
= null;
1688 for (ID
<?
, ?
> indexId
: myNotRequiringContentIndices
) {
1689 if (shouldIndexFile(file
, indexId
)) {
1692 if (fileContent
== null) {
1693 fileContent
= new FileContent(file
);
1695 updateSingleIndex(indexId
, file
, fileContent
);
1697 catch (StorageException e
) {
1699 requestRebuild(indexId
);
1703 IndexingStamp
.flushCache();
1705 if (oldStuff
&& file
instanceof NewVirtualFile
) {
1706 ((NewVirtualFile
)file
).setFlag(ALREADY_PROCESSED
, true);
1711 if (myProgressIndicator
!= null) {
1712 myProgressIndicator
.setText("Scanning files to index");
1713 myProgressIndicator
.setText2(file
.getPresentableUrl());
1720 private boolean shouldUpdateIndex(final VirtualFile file
, final ID
<?
, ?
> indexId
) {
1721 return getInputFilter(indexId
).acceptInput(file
) &&
1722 (isMock(file
) || IndexingStamp
.isFileIndexed(file
, indexId
, IndexInfrastructure
.getIndexCreationStamp(indexId
)));
1725 private boolean shouldIndexFile(final VirtualFile file
, final ID
<?
, ?
> indexId
) {
1726 return getInputFilter(indexId
).acceptInput(file
) &&
1727 (isMock(file
) || !IndexingStamp
.isFileIndexed(file
, indexId
, IndexInfrastructure
.getIndexCreationStamp(indexId
)));
1730 private boolean isUnderConfigOrSystem(VirtualFile file
) {
1731 final String filePath
= file
.getPath();
1732 if (myConfigPath
!= null && FileUtil
.startsWith(filePath
, myConfigPath
)) {
1735 if (mySystemPath
!= null && FileUtil
.startsWith(filePath
, mySystemPath
)) {
1741 private static boolean isMock(final VirtualFile file
) {
1742 return !(file
instanceof NewVirtualFile
);
1745 private boolean isTooLarge(VirtualFile file
) {
1746 if (SingleRootFileViewProvider
.isTooLarge(file
)) {
1747 final FileType type
= FileTypeManager
.getInstance().getFileTypeByFile(file
);
1748 return !myNoLimitCheckTypes
.contains(type
);
1753 public CollectingContentIterator
createContentIterator() {
1754 return new UnindexedFilesFinder();
1757 public void registerIndexableSet(IndexableFileSet set
) {
1758 myIndexableSets
.add(set
);
1761 public void removeIndexableSet(IndexableFileSet set
) {
1762 myChangedFilesUpdater
.forceUpdate(null);
1763 myIndexableSets
.remove(set
);
1767 private static PsiFile
findLatestKnownPsiForUncomittedDocument(Document doc
, Project project
) {
1768 return PsiDocumentManager
.getInstance(project
).getCachedPsiFile(doc
);
1771 private static class IndexableFilesFilter
implements InputFilter
{
1772 private final InputFilter myDelegate
;
1774 private IndexableFilesFilter(InputFilter delegate
) {
1775 myDelegate
= delegate
;
1778 public boolean acceptInput(final VirtualFile file
) {
1779 return file
instanceof VirtualFileWithId
&& myDelegate
.acceptInput(file
);
1783 private static void cleanupProcessedFlag() {
1784 final VirtualFile
[] roots
= ManagingFS
.getInstance().getRoots();
1785 for (VirtualFile root
: roots
) {
1786 cleanProcessedFlag(root
);
1790 private static void cleanProcessedFlag(final VirtualFile file
) {
1791 if (!(file
instanceof NewVirtualFile
)) return;
1793 final NewVirtualFile nvf
= (NewVirtualFile
)file
;
1794 if (file
.isDirectory()) {
1795 for (VirtualFile child
: nvf
.getCachedChildren()) {
1796 cleanProcessedFlag(child
);
1800 /* nvf.clearCachedFileType(); */
1801 nvf
.setFlag(ALREADY_PROCESSED
, false);
1805 private static class StorageGuard
{
1806 private int myHolds
= 0;
1808 public interface Holder
{
1812 private final Holder myTrueHolder
= new Holder() {
1813 public void leave() {
1814 StorageGuard
.this.leave(true);
1817 private final Holder myFalseHolder
= new Holder() {
1818 public void leave() {
1819 StorageGuard
.this.leave(false);
1823 public synchronized Holder
enter(boolean mode
) {
1825 while (myHolds
< 0) {
1829 catch (InterruptedException ignored
) {
1833 return myTrueHolder
;
1836 while (myHolds
> 0) {
1840 catch (InterruptedException ignored
) {
1844 return myFalseHolder
;
1848 private synchronized void leave(boolean mode
) {
1849 myHolds
+= (mode?
-1 : 1);