ca63aa6d1fcee56fddbfb9453859fa78399b85e0
[idea/community.git] / java / java-impl / src / com / intellij / psi / refResolve / RefResolveServiceImpl.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.psi.refResolve;
3
4 import com.intellij.ide.PowerSaveMode;
5 import com.intellij.openapi.Disposable;
6 import com.intellij.openapi.application.ApplicationListener;
7 import com.intellij.openapi.application.PathManager;
8 import com.intellij.openapi.application.ex.ApplicationEx;
9 import com.intellij.openapi.application.ex.ApplicationUtil;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.fileTypes.FileTypeRegistry;
12 import com.intellij.openapi.fileTypes.StdFileTypes;
13 import com.intellij.openapi.module.Module;
14 import com.intellij.openapi.progress.ProcessCanceledException;
15 import com.intellij.openapi.progress.ProgressIndicator;
16 import com.intellij.openapi.progress.ProgressManager;
17 import com.intellij.openapi.progress.Task;
18 import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
19 import com.intellij.openapi.progress.impl.ProgressManagerImpl;
20 import com.intellij.openapi.progress.util.ProgressIndicatorBase;
21 import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
22 import com.intellij.openapi.project.*;
23 import com.intellij.openapi.roots.ProjectFileIndex;
24 import com.intellij.openapi.startup.StartupManager;
25 import com.intellij.openapi.util.Disposer;
26 import com.intellij.openapi.util.EmptyRunnable;
27 import com.intellij.openapi.util.io.FileUtil;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.openapi.vfs.*;
30 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
31 import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
32 import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
33 import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
34 import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
35 import com.intellij.psi.*;
36 import com.intellij.psi.search.GlobalSearchScope;
37 import com.intellij.psi.util.PsiUtilCore;
38 import com.intellij.util.ArrayUtil;
39 import com.intellij.util.ExceptionUtil;
40 import com.intellij.util.Function;
41 import com.intellij.util.Processor;
42 import com.intellij.util.containers.ConcurrentBitSet;
43 import com.intellij.util.containers.ContainerUtil;
44 import com.intellij.util.containers.IntObjectMap;
45 import com.intellij.util.io.storage.HeavyProcessLatch;
46 import com.intellij.util.messages.MessageBus;
47 import com.intellij.util.messages.MessageBusConnection;
48 import gnu.trove.*;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
51 import org.jetbrains.jps.model.java.JavaSourceRootType;
52
53 import java.io.File;
54 import java.io.FileWriter;
55 import java.io.IOException;
56 import java.text.DateFormat;
57 import java.util.*;
58 import java.util.concurrent.Callable;
59 import java.util.concurrent.Future;
60 import java.util.concurrent.FutureTask;
61 import java.util.concurrent.atomic.AtomicInteger;
62 import java.util.concurrent.atomic.AtomicLong;
63
64 public class RefResolveServiceImpl extends RefResolveService implements Runnable, Disposable {
65   private static final Logger LOG = Logger.getInstance(RefResolveServiceImpl.class);
66
67   private final AtomicInteger fileCount = new AtomicInteger();
68   private final AtomicLong bytesSize = new AtomicLong();
69   private final AtomicLong refCount = new AtomicLong();
70   private final PersistentIntList storage;
71   private final Deque<VirtualFile> filesToResolve = new ArrayDeque<>(); // guarded by filesToResolve
72   private final ConcurrentBitSet fileIsInQueue = new ConcurrentBitSet();
73   private final ConcurrentBitSet fileIsResolved;
74   private final ApplicationEx myApplication;
75   private final Project myProject;
76   private volatile boolean myDisposed;
77   private volatile boolean upToDate;
78   private final AtomicInteger enableVetoes = new AtomicInteger();  // number of disable() calls. To enable the service, there should be at least corresponding number of enable() calls.
79   private final FileWriter log;
80   private final ProjectFileIndex myProjectFileIndex;
81
82   public RefResolveServiceImpl(final Project project,
83                                final MessageBus messageBus,
84                                final PsiManager psiManager,
85                                StartupManager startupManager,
86                                ApplicationEx application,
87                                ProjectFileIndex projectFileIndex) throws IOException {
88     myProject = project;
89     ((FutureTask)resolveProcess).run();
90     myApplication = application;
91     myProjectFileIndex = projectFileIndex;
92     if (ENABLED) {
93       log = new FileWriter(new File(getStorageDirectory(), "log.txt"));
94
95       File dataFile = new File(getStorageDirectory(), "data");
96       fileIsResolved = ConcurrentBitSet.readFrom(new File(getStorageDirectory(), "bitSet"));
97       log("Read resolved file bitset: " + fileIsResolved);
98
99       int maxId = FSRecords.getMaxId();
100       PersistentIntList list = new PersistentIntList(dataFile, dataFile.exists() ? 0 : maxId);
101       if (list.getSize() == maxId) {
102         storage = list;
103       }
104       else {
105         // just to be safe, re-resolve all if VFS files count changes since last restart
106         Disposer.dispose(list);
107         storage = new PersistentIntList(dataFile, maxId);
108         log("VFS maxId changed: was "+list.getSize()+"; now: "+maxId+"; re-resolving everything");
109         fileIsResolved.clear();
110       }
111       Disposer.register(this, storage);
112       if (!application.isUnitTestMode()) {
113         startupManager.runWhenProjectIsInitialized(() -> {
114           initListeners(messageBus, psiManager);
115           startThread();
116         });
117       }
118
119       Disposer.register(this, new Disposable() {
120         @Override
121         public void dispose() {
122           try {
123             save();
124             log.close();
125           }
126           catch (IOException e) {
127             LOG.error(e);
128           }
129         }
130       });
131     }
132     else {
133       log = null;
134       fileIsResolved = null;
135       storage = null;
136     }
137   }
138
139   @NotNull
140   private static List<VirtualFile> toVf(@NotNull int[] ids) {
141     List<VirtualFile> res = new ArrayList<>();
142     for (int id : ids) {
143       VirtualFile file = PersistentFS.getInstance().findFileById(id);
144       if (file != null) {
145         res.add(file);
146       }
147     }
148     return res;
149   }
150
151   @NotNull
152   private static String toVfString(@NotNull int[] backIds) {
153     List<VirtualFile> list = toVf(backIds);
154     return toVfString(list);
155   }
156
157   @NotNull
158   private static String toVfString(@NotNull Collection<VirtualFile> list) {
159     List<VirtualFile> sub = ContainerUtil.getFirstItems(new ArrayList<>(list), 100);
160     return list.size() + " files: " + StringUtil.join(sub, file -> file.getName(), ", ") + (list.size() == sub.size() ? "" : "...");
161   }
162
163   private void initListeners(@NotNull MessageBus messageBus, @NotNull PsiManager psiManager) {
164     MessageBusConnection connection = messageBus.connect(this);
165     connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
166       @Override
167       public void after(@NotNull List<? extends VFileEvent> events) {
168         fileCount.set(0);
169         List<VirtualFile> files = ContainerUtil.mapNotNull(events, (Function<VFileEvent, VirtualFile>)event -> event.getFile());
170         queue(files, "VFS events " + events.size());
171       }
172     });
173
174     psiManager.addPsiTreeChangeListener(new PsiTreeChangeAdapter() {
175       @Override
176       public void childrenChanged(@NotNull PsiTreeChangeEvent event) {
177         PsiFile file = event.getFile();
178         VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file);
179         if (virtualFile != null) {
180           queue(Collections.singletonList(virtualFile), event);
181         }
182       }
183
184       @Override
185       public void propertyChanged(@NotNull PsiTreeChangeEvent event) {
186         childrenChanged(event);
187       }
188     });
189
190     connection.subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
191       @Override
192       public void enteredDumbMode() {
193         disable();
194       }
195
196       @Override
197       public void exitDumbMode() {
198         enable();
199       }
200     });
201
202     connection.subscribe(PowerSaveMode.TOPIC, new PowerSaveMode.Listener() {
203       @Override
204       public void powerSaveStateChanged() {
205         if (PowerSaveMode.isEnabled()) {
206           enable();
207         }
208         else {
209           disable();
210         }
211       }
212     });
213
214     myApplication.addApplicationListener(new ApplicationListener() {
215       @Override
216       public void beforeWriteActionStart(@NotNull Object action) {
217         disable();
218       }
219
220       @Override
221       public void writeActionFinished(@NotNull Object action) {
222         enable();
223       }
224
225       @Override
226       public void applicationExiting() {
227         disable();
228       }
229     }, this);
230
231     VirtualFileManager.getInstance().addVirtualFileManagerListener(new VirtualFileManagerListener() {
232       @Override
233       public void beforeRefreshStart(boolean asynchronous) {
234         disable();
235       }
236
237       @Override
238       public void afterRefreshFinish(boolean asynchronous) {
239         enable();
240       }
241     }, this);
242
243     HeavyProcessLatch.INSTANCE.addListener(new HeavyProcessLatch.HeavyProcessListener() {
244       @Override
245       public void processFinished() {
246         wakeUp();
247       }
248     }, this);
249   }
250
251   // return true if file was added to queue
252   private boolean queueIfNeeded(VirtualFile virtualFile, @NotNull Project project) {
253     return toResolve(virtualFile, project) && queueUpdate(virtualFile);
254   }
255
256   private boolean toResolve(VirtualFile virtualFile, @NotNull Project project) {
257     if (virtualFile != null &&
258         virtualFile.isValid() &&
259         project.isInitialized() &&
260         myProjectFileIndex.isInSourceContent(virtualFile) &&
261         isSupportedFileType(virtualFile)) {
262       return true;
263     }
264
265     // else mark it as resolved so we will not have to check it again
266     if (virtualFile instanceof VirtualFileWithId) {
267       int id = getAbsId(virtualFile);
268       fileIsResolved.set(id);
269     }
270
271     return false;
272   }
273
274   public static boolean isSupportedFileType(@NotNull VirtualFile virtualFile) {
275     if (virtualFile.isDirectory()) return true;
276     if (FileTypeRegistry.getInstance().isFileOfType(virtualFile, StdFileTypes.JAVA)) return true;
277     if (FileTypeRegistry.getInstance().isFileOfType(virtualFile, StdFileTypes.XML) && !ProjectUtil.isProjectOrWorkspaceFile(virtualFile)) return true;
278     final String extension = virtualFile.getExtension();
279     if ("groovy".equals(extension) || "kt".equals(extension)) return true;
280     return false;
281   }
282
283   @NotNull
284   private File getStorageDirectory() {
285     String dirName = myProject.getName() + "."+Integer.toHexString(myProject.getPresentableUrl().hashCode());
286     File dir = new File(PathManager.getSystemPath(), "refs/" + dirName);
287     FileUtil.createDirectory(dir);
288     return dir;
289   }
290
291
292   private void log(String m) {
293     //System.out.println(m);
294     logf(m);
295   }
296
297   private void logf(String m) {
298     if (LOG.isDebugEnabled()) {
299       try {
300         log.write(DateFormat.getDateTimeInstance().format(new Date()) + " "+m+/*"    ; gap="+storage.gap+*/"\n");
301       }
302       catch (IOException e) {
303         LOG.error(e);
304       }
305     }
306   }
307
308   private void flushLog() {
309     try {
310       log.flush();
311     }
312     catch (IOException e) {
313       LOG.error(e);
314     }
315   }
316
317   // return true if file was added to queue
318   private boolean queueUpdate(@NotNull VirtualFile file) {
319     synchronized (filesToResolve) {
320       if (!(file instanceof VirtualFileWithId)) return false;
321       int fileId = getAbsId(file);
322       countAndMarkUnresolved(file, new LinkedHashSet<>(), true);
323       boolean alreadyAdded = fileIsInQueue.set(fileId);
324       if (!alreadyAdded) {
325         filesToResolve.add(file);
326       }
327       upToDate = false;
328       wakeUpUnderLock();
329       return !alreadyAdded;
330     }
331   }
332
333   private void wakeUp() {
334     synchronized (filesToResolve) {
335       wakeUpUnderLock();
336     }
337   }
338
339   private void wakeUpUnderLock() {
340     filesToResolve.notifyAll();
341   }
342
343   private void waitForQueue() throws InterruptedException {
344     synchronized (filesToResolve) {
345       filesToResolve.wait(1000);
346     }
347   }
348
349   private void startThread() {
350     new Thread(this, "Ref resolve service").start();
351     upToDate = true;
352     queueUnresolvedFilesSinceLastRestart();
353   }
354
355   private void queueUnresolvedFilesSinceLastRestart() {
356     PersistentFS fs = PersistentFS.getInstance();
357     int maxId = FSRecords.getMaxId();
358     TIntArrayList list = new TIntArrayList();
359     for (int id= fileIsResolved.nextClearBit(1); id >= 0 && id < maxId; id = fileIsResolved.nextClearBit(id + 1)) {
360       int nextSetBit = fileIsResolved.nextSetBit(id);
361       int endOfRun = Math.min(maxId, nextSetBit == -1 ? maxId : nextSetBit);
362       do {
363         VirtualFile virtualFile = fs.findFileById(id);
364         if (queueIfNeeded(virtualFile, myProject)) {
365           list.add(id);
366         }
367         else {
368           fileIsResolved.set(id);
369         }
370       }
371       while (++id < endOfRun);
372     }
373     log("Initially added to resolve " + toVfString(list.toNativeArray()));
374   }
375
376   @Override
377   public void dispose() {
378     myDisposed = true;
379   }
380
381   private void save() throws IOException {
382     log("Saving resolved file bitset: "+fileIsResolved);
383     fileIsResolved.writeTo(new File(getStorageDirectory(), "bitSet"));
384     log("list.size = " + storage.getSize());
385   }
386
387   private volatile Future<?> resolveProcess = new FutureTask<>(EmptyRunnable.getInstance(), null); // write from EDT only
388
389   @Override
390   public void run() {
391     while (!myDisposed) {
392       boolean isEmpty;
393       synchronized (filesToResolve) {
394         isEmpty = filesToResolve.isEmpty();
395       }
396       if (enableVetoes.get() > 0 ||
397           isEmpty ||
398           !resolveProcess.isDone() ||
399           HeavyProcessLatch.INSTANCE.isRunning() ||
400           PsiDocumentManager.getInstance(myProject).hasUncommitedDocuments()) {
401         try {
402           waitForQueue();
403         }
404         catch (InterruptedException e) {
405           break;
406         }
407         continue;
408       }
409       final Set<VirtualFile> files = pollFilesToResolve();
410       if (files.isEmpty()) continue;
411
412       upToDate = false;
413
414       myApplication.invokeLater(() -> {
415         if (!resolveProcess.isDone()) return;
416         log("Started to resolve " + files.size() + " files");
417
418         Task.Backgroundable backgroundable = new Task.Backgroundable(myProject, "Resolving files...", false) {
419           @Override
420           public void run(@NotNull final ProgressIndicator indicator) {
421             if (!myApplication.isDisposed()) {
422               processBatch(indicator, files);
423             }
424           }
425         };
426         ProgressIndicator indicator;
427         if (files.size() > 1) {
428           //show progress
429           indicator = new BackgroundableProcessIndicator(backgroundable);
430         }
431         else {
432           indicator = new MyProgress();
433         }
434         resolveProcess = ((ProgressManagerImpl)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(backgroundable, indicator, null);
435       }, myProject.getDisposed());
436
437       flushLog();
438     }
439   }
440
441   private volatile int resolvedInPreviousBatch;
442   private void processBatch(@NotNull final ProgressIndicator indicator, @NotNull Set<VirtualFile> files) {
443     assert !myApplication.isDispatchThread();
444     final int resolvedInPreviousBatch = this.resolvedInPreviousBatch;
445     final int totalSize = files.size() + resolvedInPreviousBatch;
446     final IntObjectMap<int[]> fileToForwardIds = ContainerUtil.createConcurrentIntObjectMap();
447     final Set<VirtualFile> toProcess = Collections.synchronizedSet(files);
448     indicator.setIndeterminate(false);
449     ProgressIndicatorUtils.forceWriteActionPriority(indicator, (Disposable)indicator);
450     long start = System.currentTimeMillis();
451     Processor<VirtualFile> processor = file -> {
452       double fraction = 1 - toProcess.size() * 1.0 / totalSize;
453       indicator.setFraction(fraction);
454       try {
455         if (!file.isDirectory() && toResolve(file, myProject)) {
456           int fileId = getAbsId(file);
457           int i = totalSize - toProcess.size();
458           indicator.setText(i + "/" + totalSize + ": Resolving " + file.getPresentableUrl());
459           int[] forwardIds = processFile(file, fileId, indicator);
460           if (forwardIds == null) {
461             //queueUpdate(file);
462             return false;
463           }
464           fileToForwardIds.put(fileId, forwardIds);
465         }
466         toProcess.remove(file);
467         return true;
468       }
469       catch (RuntimeException e) {
470         indicator.checkCanceled();
471       }
472       return true;
473     };
474     boolean success = true;
475     try {
476       success = processFilesConcurrently(files, indicator, processor);
477     }
478     finally {
479       this.resolvedInPreviousBatch = toProcess.isEmpty() ? 0 : totalSize - toProcess.size();
480       queue(toProcess, "re-added after fail. success=" + success);
481       storeIds(fileToForwardIds);
482
483       long end = System.currentTimeMillis();
484       log("Resolved batch of " + (totalSize - toProcess.size()) + " from " + totalSize + " files in " + ((end - start) / 1000) + "sec. (Gap: " + storage.gap+")");
485       synchronized (filesToResolve) {
486         upToDate = filesToResolve.isEmpty();
487         log("upToDate = " + upToDate);
488         if (upToDate) {
489           for (Listener listener : myListeners) {
490             listener.allFilesResolved();
491           }
492         }
493       }
494     }
495   }
496
497   private boolean processFilesConcurrently(@NotNull Set<VirtualFile> files,
498                                            @NotNull final ProgressIndicator indicator,
499                                            @NotNull final Processor<VirtualFile> processor) {
500     final List<VirtualFile> fileList = new ArrayList<>(files);
501     // fine but grabs all CPUs
502     //return JobLauncher.getInstance().invokeConcurrentlyUnderProgress(fileList, indicator, false, false, processor);
503
504     int parallelism = CacheUpdateRunner.indexingThreadCount();
505     final Callable<Boolean> processFileFromSet = () -> {
506       final boolean[] result = {true};
507       ProgressManager.getInstance().executeProcessUnderProgress(() -> {
508         while (true) {
509           ProgressManager.checkCanceled();
510           VirtualFile file;
511           synchronized (fileList) {
512             file = fileList.isEmpty() ? null : fileList.remove(fileList.size() - 1);
513           }
514           if (file == null) {
515             break;
516           }
517           if (!processor.process(file)) {
518             result[0] = false;
519             break;
520           }
521         }
522       }, indicator);
523       return result[0];
524     };
525     List<Future<Boolean>> futures = ContainerUtil.map(Collections.nCopies(parallelism, ""), s -> myApplication.executeOnPooledThread(processFileFromSet));
526
527     List<Boolean> results = ContainerUtil.map(futures, future -> {
528       try {
529         return future.get();
530       }
531       catch (Exception e) {
532         LOG.error(e);
533       }
534       return false;
535     });
536
537     return !ContainerUtil.exists(results, result -> {
538       return result != null && !result;  // null means PCE
539     });
540   }
541
542   @NotNull
543   private Set<VirtualFile> pollFilesToResolve() {
544     Set<VirtualFile> set;
545     synchronized (filesToResolve) {
546       int queuedSize = filesToResolve.size();
547       set = new LinkedHashSet<>(queuedSize);
548       // someone might have cleared this bit to mark file as processed
549       for (VirtualFile file : filesToResolve) {
550         if (fileIsInQueue.clear(getAbsId(file))) {
551           set.add(file);
552         }
553       }
554       filesToResolve.clear();
555     }
556     return countAndMarkUnresolved(set, false);
557   }
558
559   private static int getAbsId(@NotNull VirtualFile file) {
560     return ((VirtualFileWithId)file).getId();
561   }
562
563   @NotNull
564   private Set<VirtualFile> countAndMarkUnresolved(@NotNull Collection<VirtualFile> files, boolean inDbOnly) {
565     Set<VirtualFile> result = new LinkedHashSet<>();
566     for (VirtualFile file : files) {
567       countAndMarkUnresolved(file, result, inDbOnly);
568     }
569     return result;
570   }
571
572   private void countAndMarkUnresolved(@NotNull VirtualFile file, @NotNull final Set<VirtualFile> result, final boolean inDbOnly) {
573     if (file.isDirectory()) {
574       VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor<Void>() {
575         @Override
576         public boolean visitFile(@NotNull VirtualFile file) {
577           return doCountAndMarkUnresolved(file, result);
578         }
579
580         @Nullable
581         @Override
582         public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
583           return inDbOnly ? ((NewVirtualFile)file).iterInDbChildren() : null;
584         }
585       });
586     }
587     else {
588       doCountAndMarkUnresolved(file, result);
589     }
590   }
591
592   // return true if continue to process sub-directories of the {@code file}, false if the file is already processed
593   private boolean doCountAndMarkUnresolved(@NotNull VirtualFile file, @NotNull Set<VirtualFile> result) {
594     if (file.isDirectory()) {
595       fileIsResolved.set(getAbsId(file));
596       return result.add(file);
597     }
598     if (toResolve(file, myProject)) {
599       result.add(file);
600       fileIsResolved.clear(getAbsId(file));
601     }
602     return true;
603   }
604
605   private void enable() {
606     // decrement but only if it's positive
607     int vetoes;
608     do {
609       vetoes = enableVetoes.get();
610       if (vetoes == 0) break;
611     } while(!enableVetoes.compareAndSet(vetoes, vetoes-1));
612     wakeUp();
613   }
614
615   private void disable() {
616     enableVetoes.incrementAndGet();
617     wakeUp();
618   }
619
620   // returns list of resolved files if updated successfully, or null if write action or dumb mode started
621   private int[] processFile(@NotNull final VirtualFile file, int fileId, @NotNull final ProgressIndicator indicator) {
622     final TIntHashSet forward;
623     try {
624       forward = calcForwardRefs(file, indicator);
625     }
626     catch (IndexNotReadyException | ApplicationUtil.CannotRunReadActionException e) {
627       return null;
628     }
629     catch (ProcessCanceledException e) {
630       throw e;
631     }
632     catch (Exception e) {
633       log(ExceptionUtil.getThrowableText(e));
634       flushLog();
635       return null;
636     }
637
638     int[] forwardIds = forward.toArray();
639     fileIsResolved.set(fileId);
640     logf("  ---- " + file.getPresentableUrl() + " processed. forwardIds: " + toVfString(forwardIds));
641     for (Listener listener : myListeners) {
642       listener.fileResolved(file);
643     }
644     return forwardIds;
645   }
646
647   private void storeIds(@NotNull IntObjectMap<int[]> fileToForwardIds) {
648     int forwardSize = 0;
649     int backwardSize = 0;
650     final TIntObjectHashMap<TIntArrayList> fileToBackwardIds = new TIntObjectHashMap<>(fileToForwardIds.size());
651     for (IntObjectMap.Entry<int[]> entry : fileToForwardIds.entrySet()) {
652       int fileId = entry.getKey();
653       int[] forwardIds = entry.getValue();
654       forwardSize += forwardIds.length;
655       for (int forwardId : forwardIds) {
656         TIntArrayList backIds = fileToBackwardIds.get(forwardId);
657         if (backIds == null) {
658           backIds = new TIntArrayList();
659           fileToBackwardIds.put(forwardId, backIds);
660         }
661         backIds.add(fileId);
662         backwardSize++;
663       }
664     }
665     log("backwardSize = " + backwardSize);
666     log("forwardSize = " + forwardSize);
667     log("fileToForwardIds.size() = "+fileToForwardIds.size());
668     log("fileToBackwardIds.size() = "+fileToBackwardIds.size());
669     assert forwardSize == backwardSize;
670
671     // wrap in read action so that sudden quit (in write action) would not interrupt us
672     myApplication.runReadAction(() -> {
673       if (!myApplication.isDisposed()) {
674         fileToBackwardIds.forEachEntry(new TIntObjectProcedure<TIntArrayList>() {
675           @Override
676           public boolean execute(int fileId, TIntArrayList backIds) {
677             storage.addAll(fileId, backIds.toNativeArray());
678             return true;
679           }
680         });
681       }
682     });
683   }
684
685
686   @NotNull
687   private TIntHashSet calcForwardRefs(@NotNull final VirtualFile virtualFile, @NotNull final ProgressIndicator indicator)
688     throws IndexNotReadyException, ApplicationUtil.CannotRunReadActionException {
689
690     final TIntHashSet forward = new TIntHashSet();
691
692     final PsiFile psiFile = ApplicationUtil.tryRunReadAction(() -> {
693       if (myProject.isDisposed()) throw new ProcessCanceledException();
694       if (fileCount.incrementAndGet() % 100 == 0) {
695         PsiManager.getInstance(myProject).dropResolveCaches();
696         try {
697           storage.flush();
698           log.flush();
699         }
700         catch (IOException e) {
701           LOG.error(e);
702         }
703       }
704
705       return PsiManager.getInstance(myProject).findFile(virtualFile);
706     });
707     final int fileId = getAbsId(virtualFile);
708     if (psiFile != null) {
709       bytesSize.addAndGet(virtualFile.getLength());
710       final Set<PsiElement> resolved = new THashSet<>();
711       ApplicationUtil.tryRunReadAction(new Runnable() {
712         @Override
713         public void run() {
714           indicator.checkCanceled();
715
716           if (psiFile instanceof PsiJavaFile) {
717             psiFile.accept(new JavaRecursiveElementWalkingVisitor() {
718               @Override
719               public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
720                 indicator.checkCanceled();
721                 resolveReference(reference, resolved);
722
723                 super.visitReferenceElement(reference);
724               }
725             });
726           }
727           else {
728             psiFile.accept(new PsiRecursiveElementWalkingVisitor() {
729               @Override
730               public void visitElement(PsiElement element) {
731                 for (PsiReference reference : element.getReferences()) {
732                   indicator.checkCanceled();
733                   resolveReference(reference, resolved);
734                 }
735                 super.visitElement(element);
736               }
737             });
738           }
739
740           indicator.checkCanceled();
741           for (PsiElement element : resolved) {
742             PsiFile file = element.getContainingFile();
743             addIdAndSuperClasses(file, forward);
744           }
745         }
746       });
747     }
748
749     forward.remove(fileId);
750     return forward;
751   }
752
753   private void resolveReference(@NotNull PsiReference reference, @NotNull Set<PsiElement> resolved) {
754     PsiElement element = reference.resolve();
755     if (element != null) {
756       resolved.add(element);
757     }
758     refCount.incrementAndGet();
759   }
760
761   private static void addIdAndSuperClasses(PsiFile file, @NotNull TIntHashSet forward) {
762     if (file instanceof PsiJavaFile && file.getName().equals("Object.class") && ((PsiJavaFile)file).getPackageName().equals("java.lang")) {
763       return;
764     }
765     VirtualFile virtualFile = PsiUtilCore.getVirtualFile(file);
766     if (virtualFile instanceof VirtualFileWithId && forward.add(getAbsId(virtualFile)) && file instanceof PsiClassOwner) {
767       for (PsiClass aClass : ((PsiClassOwner)file).getClasses()) {
768         for (PsiClass superClass : aClass.getSupers()) {
769           addIdAndSuperClasses(superClass.getContainingFile(), forward);
770         }
771       }
772     }
773   }
774
775   @Override
776   @Nullable
777   public int[] getBackwardIds(@NotNull VirtualFileWithId file) {
778     if (!isUpToDate()) return null;
779     int fileId = getAbsId((VirtualFile)file);
780     return storage.get(fileId);
781   }
782
783   private String prevLog = "";
784   private static final Set<JavaSourceRootType> SOURCE_ROOTS = ContainerUtil.newTroveSet(JavaSourceRootType.SOURCE, JavaSourceRootType.TEST_SOURCE);
785
786   @NotNull
787   @Override
788   public GlobalSearchScope restrictByBackwardIds(@NotNull final VirtualFile virtualFile, @NotNull GlobalSearchScope scope) {
789     final int[] backIds = RefResolveService.getInstance(myProject).getBackwardIds((VirtualFileWithId)virtualFile);
790     if (backIds == null) {
791       return scope;
792     }
793     String files = toVfString(backIds);
794     String log = "Restricting scope of " + virtualFile.getName() + " to " + files;
795     if (!log.equals(prevLog)) {
796       log(log);
797       flushLog();
798       prevLog = log;
799     }
800     GlobalSearchScope restrictedByBackwardIds = new GlobalSearchScope() {
801       @Override
802       public boolean contains(@NotNull VirtualFile file) {
803         if (!(file instanceof VirtualFileWithId)
804             || file.equals(virtualFile)
805             || ArrayUtil.indexOf(backIds, getAbsId(file)) != -1) return true;
806         return false & !myProjectFileIndex.isUnderSourceRootOfType(file, SOURCE_ROOTS); // filter out source file which we know for sure does not reference the element
807       }
808
809       @Override
810       public boolean isSearchInModuleContent(@NotNull Module aModule) {
811         return true;
812       }
813
814       @Override
815       public boolean isSearchInLibraries() {
816         return false;
817       }
818     };
819     return scope.intersectWith(restrictedByBackwardIds);
820   }
821
822   @Override
823   public boolean queue(@NotNull Collection<VirtualFile> files, @NotNull Object reason) {
824     if (files.isEmpty()) {
825       return false;
826     }
827     boolean queued = false;
828     List<VirtualFile> added = new ArrayList<>(files.size());
829     for (VirtualFile file : files) {
830       boolean wasAdded = queueIfNeeded(file, myProject);
831       if (wasAdded) {
832         added.add(file);
833       }
834       queued |= wasAdded;
835     }
836     if (queued) {
837       log("Queued to resolve (from " + reason + "): " + toVfString(added));
838       flushLog();
839     }
840     return queued;
841   }
842
843   @Override
844   public boolean isUpToDate() {
845     return ENABLED && !myDisposed && upToDate;
846   }
847
848   @Override
849   public int getQueueSize() {
850     synchronized (filesToResolve) {
851       return filesToResolve.size();
852     }
853   }
854
855   private final List<Listener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
856   @Override
857   public void addListener(@NotNull Disposable parent, @NotNull final Listener listener) {
858     myListeners.add(listener);
859     Disposer.register(parent, new Disposable() {
860       @Override
861       public void dispose() {
862         myListeners.remove(listener);
863       }
864     });
865   }
866
867   private static class MyProgress extends ProgressIndicatorBase implements Disposable{
868     @Override
869     public void dispose() {
870     }
871   }
872 }