javac ast indices: search for lambda expressions; version #2 (with lambda hierarchy)
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / backwardRefs / CompilerReferenceServiceImpl.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.compiler.backwardRefs;
17
18 import com.intellij.compiler.CompilerDirectHierarchyInfo;
19 import com.intellij.compiler.CompilerReferenceService;
20 import com.intellij.compiler.server.BuildManager;
21 import com.intellij.compiler.server.BuildManagerListener;
22 import com.intellij.lang.injection.InjectedLanguageManager;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.application.ReadAction;
25 import com.intellij.openapi.compiler.*;
26 import com.intellij.openapi.fileTypes.FileType;
27 import com.intellij.openapi.module.Module;
28 import com.intellij.openapi.progress.ProgressIndicator;
29 import com.intellij.openapi.progress.ProgressManager;
30 import com.intellij.openapi.progress.Task;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.roots.ProjectFileIndex;
33 import com.intellij.openapi.roots.ProjectRootManager;
34 import com.intellij.openapi.roots.impl.LibraryScopeCache;
35 import com.intellij.openapi.util.Couple;
36 import com.intellij.openapi.util.ModificationTracker;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.vfs.VirtualFileWithId;
39 import com.intellij.psi.PsiElement;
40 import com.intellij.psi.PsiNamedElement;
41 import com.intellij.psi.search.GlobalSearchScope;
42 import com.intellij.psi.util.CachedValueProvider;
43 import com.intellij.psi.util.CachedValuesManager;
44 import com.intellij.psi.util.PsiModificationTracker;
45 import com.intellij.psi.util.PsiUtilCore;
46 import com.intellij.util.containers.ConcurrentFactoryMap;
47 import com.intellij.util.indexing.FileBasedIndex;
48 import gnu.trove.THashSet;
49 import gnu.trove.TIntHashSet;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52 import org.jetbrains.annotations.TestOnly;
53 import org.jetbrains.jps.backwardRefs.LightRef;
54
55 import java.util.*;
56 import java.util.concurrent.atomic.LongAdder;
57 import java.util.concurrent.locks.Lock;
58 import java.util.concurrent.locks.ReentrantReadWriteLock;
59 import java.util.stream.Collectors;
60 import java.util.stream.Stream;
61
62 import static com.intellij.psi.search.GlobalSearchScope.getScopeRestrictedByFileTypes;
63 import static com.intellij.psi.search.GlobalSearchScope.notScope;
64
65 public class CompilerReferenceServiceImpl extends CompilerReferenceService implements ModificationTracker {
66   private final Set<FileType> myFileTypes;
67   private final DirtyModulesHolder myDirtyModulesHolder;
68   private final ProjectFileIndex myProjectFileIndex;
69   private final LongAdder myCompilationCount = new LongAdder();
70   private final ReentrantReadWriteLock myLock = new ReentrantReadWriteLock();
71   private final Lock myReadDataLock = myLock.readLock();
72   private final Lock myOpenCloseLock = myLock.writeLock();
73
74   private volatile CompilerReferenceReader myReader;
75
76   public CompilerReferenceServiceImpl(Project project) {
77     super(project);
78
79     myProjectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
80     myFileTypes = Stream.of(LanguageLightRefAdapter.INSTANCES).flatMap(a -> a.getFileTypes().stream()).collect(Collectors.toSet());
81     myDirtyModulesHolder = new DirtyModulesHolder(this);
82   }
83
84   @Override
85   public void projectOpened() {
86     if (isEnabled()) {
87       myProject.getMessageBus().connect(myProject).subscribe(BuildManagerListener.TOPIC, new BuildManagerListener() {
88         @Override
89         public void buildStarted(Project project, UUID sessionId, boolean isAutomake) {
90           myDirtyModulesHolder.compilerActivityStarted();
91           closeReaderIfNeed();
92         }
93       });
94
95       CompilerManager compilerManager = CompilerManager.getInstance(myProject);
96       compilerManager.addCompilationStatusListener(new CompilationStatusListener() {
97         @Override
98         public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
99           compilationFinished(errors, compileContext);
100         }
101
102         @Override
103         public void automakeCompilationFinished(int errors, int warnings, CompileContext compileContext) {
104           compilationFinished(errors, compileContext);
105         }
106
107         private void compilationFinished(int errors, CompileContext context) {
108           Runnable compilationFinished = () -> {
109             final Module[] compilationModules = context.getCompileScope().getAffectedModules();
110             Set<Module> modulesWithErrors;
111             if (errors != 0) {
112               modulesWithErrors = Stream.of(context.getMessages(CompilerMessageCategory.ERROR))
113                 .map(CompilerMessage::getVirtualFile)
114                 .distinct()
115                 .map(f -> f == null ? null : myProjectFileIndex.getModuleForFile(f))
116                 .collect(Collectors.toSet());
117             }
118             else {
119               modulesWithErrors = Collections.emptySet();
120             }
121             if (modulesWithErrors.contains(null) /*unknown error location*/) {
122               myDirtyModulesHolder.compilerActivityFinished(Module.EMPTY_ARRAY, compilationModules);
123             } else {
124               myDirtyModulesHolder.compilerActivityFinished(compilationModules, modulesWithErrors.toArray(Module.EMPTY_ARRAY));
125             }
126
127             myCompilationCount.increment();
128             openReaderIfNeed();
129           };
130           executeOnBuildThread(compilationFinished);
131         }
132       });
133
134       myDirtyModulesHolder.installVFSListener();
135
136       ProgressManager.getInstance().run(new Task.Backgroundable(myProject, CompilerBundle.message("compiler.ref.service.validation.task.name")) {
137           @Override
138           public void run(@NotNull ProgressIndicator indicator) {
139             indicator.setText(CompilerBundle.message("compiler.ref.service.validation.progress.text"));
140             CompileScope projectCompileScope = compilerManager.createProjectCompileScope(myProject);
141             boolean isUpToDate = compilerManager.isUpToDate(projectCompileScope);
142             executeOnBuildThread(() -> {
143               if (isUpToDate) {
144                 myDirtyModulesHolder.compilerActivityFinished(projectCompileScope.getAffectedModules(), Module.EMPTY_ARRAY);
145                 myCompilationCount.increment();
146                 openReaderIfNeed();
147               }
148               else {
149                 myDirtyModulesHolder.compilerActivityFinished(Module.EMPTY_ARRAY, projectCompileScope.getAffectedModules());
150               }
151             });
152           }
153         });
154     }
155   }
156
157   @Override
158   public void projectClosed() {
159     closeReaderIfNeed();
160   }
161
162   @Nullable
163   @Override
164   public GlobalSearchScope getScopeWithoutCodeReferences(@NotNull PsiElement element) {
165     if (!isServiceEnabledFor(element)) return null;
166
167     return CachedValuesManager.getCachedValue(element,
168                                               () -> CachedValueProvider.Result.create(calculateScopeWithoutReferences(element),
169                                                                                       PsiModificationTracker.MODIFICATION_COUNT,
170                                                                                       this));
171   }
172
173   @Nullable
174   @Override
175   public <T extends PsiElement> CompilerDirectHierarchyInfo<T> getDirectInheritors(@NotNull PsiNamedElement aClass,
176                                                                                    @NotNull GlobalSearchScope useScope,
177                                                                                    @NotNull GlobalSearchScope searchScope,
178                                                                                    @NotNull FileType searchFileType) {
179     return getHierarchyInfo(aClass, useScope, searchScope, searchFileType, CompilerHierarchySearchType.DIRECT_INHERITOR);
180   }
181
182   @Nullable
183   @Override
184   public <T extends PsiElement> CompilerDirectHierarchyInfo<T> getFunExpressions(@NotNull PsiNamedElement functionalInterface,
185                                                                                  @NotNull GlobalSearchScope useScope,
186                                                                                  @NotNull GlobalSearchScope searchScope,
187                                                                                  @NotNull FileType searchFileType) {
188     return getHierarchyInfo(functionalInterface, useScope, searchScope, searchFileType, CompilerHierarchySearchType.FUNCTIONAL_EXPRESSION);
189   }
190
191   @Nullable
192   private <T extends PsiElement> CompilerDirectHierarchyInfo<T> getHierarchyInfo(@NotNull PsiNamedElement aClass,
193                                                                                  @NotNull GlobalSearchScope useScope,
194                                                                                  @NotNull GlobalSearchScope searchScope,
195                                                                                  @NotNull FileType searchFileType,
196                                                                                  @NotNull CompilerHierarchySearchType searchType) {
197       if (!isServiceEnabledFor(aClass) || searchScope == LibraryScopeCache.getInstance(myProject).getLibrariesOnlyScope()) return null;
198
199     Couple<Map<VirtualFile, T[]>> directInheritorsAndCandidates =
200       CachedValuesManager.getCachedValue(aClass, () -> CachedValueProvider.Result.create(
201         new ConcurrentFactoryMap<HierarchySearchKey, Couple<Map<VirtualFile, T[]>>>() {
202         @Nullable
203         @Override
204         protected Couple<Map<VirtualFile, T[]>> create(HierarchySearchKey key) {
205           return calculateDirectInheritors(aClass,
206                                            useScope,
207                                            key.mySearchFileType,
208                                            key.mySearchType);
209         }
210       }, PsiModificationTracker.MODIFICATION_COUNT, this)).get(new HierarchySearchKey(searchType, searchFileType));
211
212     if (directInheritorsAndCandidates == null) return null;
213     GlobalSearchScope dirtyScope = myDirtyModulesHolder.getDirtyScope();
214     if (ElementPlace.LIB == ElementPlace.get(aClass.getContainingFile().getVirtualFile(), myProjectFileIndex)) {
215       dirtyScope = dirtyScope.union(LibraryScopeCache.getInstance(myProject).getLibrariesOnlyScope());
216     }
217     return new CompilerHierarchyInfoImpl<>(directInheritorsAndCandidates, dirtyScope, searchScope);
218   }
219
220   private boolean isServiceEnabledFor(PsiElement element) {
221     return isServiceEnabled() && !InjectedLanguageManager.getInstance(myProject).isInjectedFragment(element.getContainingFile());
222   }
223
224   private boolean isServiceEnabled() {
225     return myReader != null && isEnabled();
226   }
227
228   private <T extends PsiElement> Couple<Map<VirtualFile, T[]>> calculateDirectInheritors(@NotNull PsiNamedElement aClass,
229                                                                                          @NotNull GlobalSearchScope useScope,
230                                                                                          @NotNull FileType searchFileType,
231                                                                                          @NotNull CompilerHierarchySearchType searchType) {
232
233     final CompilerElementInfo searchElementInfo = asCompilerElements(aClass, false);
234     if (searchElementInfo == null) return null;
235     LightRef searchElement = searchElementInfo.searchElements[0];
236
237     myReadDataLock.lock();
238     try {
239       if (myReader == null) return null;
240       return myReader.getDirectInheritors(aClass, searchElement, useScope, myDirtyModulesHolder.getDirtyScope(), myProject, searchFileType, searchType);
241     } finally {
242       myReadDataLock.unlock();
243     }
244   }
245
246   @Nullable
247   private GlobalSearchScope calculateScopeWithoutReferences(@NotNull PsiElement element) {
248     TIntHashSet referentFileIds = getReferentFileIds(element);
249     if (referentFileIds == null) return null;
250
251     return getScopeRestrictedByFileTypes(new ScopeWithoutReferencesOnCompilation(referentFileIds).intersectWith(notScope(myDirtyModulesHolder.getDirtyScope())),
252                                          myFileTypes.toArray(new FileType[myFileTypes.size()]));
253   }
254
255   @Nullable
256   private TIntHashSet getReferentFileIds(@NotNull PsiElement element) {
257     final CompilerElementInfo compilerElementInfo = asCompilerElements(element, true);
258     if (compilerElementInfo == null) return null;
259
260     myReadDataLock.lock();
261     try {
262       if (myReader == null) return null;
263       TIntHashSet referentFileIds = new TIntHashSet();
264       for (LightRef ref : compilerElementInfo.searchElements) {
265         final TIntHashSet referents = myReader.findReferentFileIds(ref, compilerElementInfo.place == ElementPlace.SRC);
266         if (referents == null) return null;
267         referentFileIds.addAll(referents.toArray());
268       }
269       return referentFileIds;
270
271     } finally {
272       myReadDataLock.unlock();
273     }
274   }
275
276   @Nullable
277   private CompilerElementInfo asCompilerElements(@NotNull PsiElement psiElement, boolean buildHierarchyForLibraryElements) {
278     myReadDataLock.lock();
279     try {
280       if (myReader == null) return null;
281       VirtualFile file = PsiUtilCore.getVirtualFile(psiElement);
282       ElementPlace place = ElementPlace.get(file, myProjectFileIndex);
283       if (place == null || (place == ElementPlace.SRC && myDirtyModulesHolder.contains(file))) {
284         return null;
285       }
286       final LanguageLightRefAdapter<?, ?> adapter = findAdapterForFileType(file.getFileType());
287       final LightRef ref = ReadAction.compute(() -> adapter.asLightUsage(psiElement, myReader.getNameEnumerator()));
288       if (ref == null) return null;
289       if (place == ElementPlace.LIB && buildHierarchyForLibraryElements) {
290         final List<LightRef> elements = adapter.getHierarchyRestrictedToLibraryScope(ref,
291                                                                                      psiElement,
292                                                                                      myReader.getNameEnumerator(),
293                                                                                      LibraryScopeCache.getInstance(myProject).getLibrariesOnlyScope());
294         final LightRef[] fullHierarchy = new LightRef[elements.size() + 1];
295         fullHierarchy[0] = ref;
296         int i = 1;
297         for (LightRef element : elements) {
298           fullHierarchy[i++] = element;
299         }
300         return new CompilerElementInfo(place, fullHierarchy);
301       }
302       else {
303         return new CompilerElementInfo(place, ref);
304       }
305     } finally {
306       myReadDataLock.unlock();
307     }
308
309
310   }
311
312   private void closeReaderIfNeed() {
313     myOpenCloseLock.lock();
314     try {
315       if (myReader != null) {
316         myReader.close();
317         myReader = null;
318       }
319     } finally {
320       myOpenCloseLock.unlock();
321     }
322   }
323
324   private void openReaderIfNeed() {
325     myOpenCloseLock.lock();
326     try {
327       if (myProject.isOpen()) {
328         myReader = CompilerReferenceReader.create(myProject);
329       }
330     } finally {
331       myOpenCloseLock.unlock();
332     }
333   }
334
335   @TestOnly
336   @Nullable
337   public Set<VirtualFile> getReferentFiles(@NotNull PsiElement element) {
338     FileBasedIndex fileIndex = FileBasedIndex.getInstance();
339     final TIntHashSet ids = getReferentFileIds(element);
340     if (ids == null) return null;
341     Set<VirtualFile> fileSet = new THashSet<>();
342     ids.forEach(id -> {
343       final VirtualFile vFile = fileIndex.findFileById(myProject, id);
344       assert vFile != null;
345       fileSet.add(vFile);
346       return true;
347     });
348     return fileSet;
349   }
350
351   ProjectFileIndex getFileIndex() {
352     return myProjectFileIndex;
353   }
354
355   Set<FileType> getFileTypes() {
356     return myFileTypes;
357   }
358
359   Project getProject() {
360     return myProject;
361   }
362
363   static LanguageLightRefAdapter findAdapterForFileType(@NotNull FileType fileType) {
364     for (LanguageLightRefAdapter adapter : LanguageLightRefAdapter.INSTANCES) {
365       if (adapter.getFileTypes().contains(fileType)) {
366         return adapter;
367       }
368     }
369     throw new AssertionError("adapter is not found for: " + fileType);
370   }
371
372   private static void executeOnBuildThread(Runnable compilationFinished) {
373     if (ApplicationManager.getApplication().isUnitTestMode()) {
374       compilationFinished.run();
375     } else {
376       BuildManager.getInstance().runCommand(compilationFinished);
377     }
378   }
379
380   private enum ElementPlace {
381     SRC, LIB;
382
383     private static ElementPlace get(VirtualFile file, ProjectFileIndex index) {
384       if (file == null) return null;
385       return index.isInSourceContent(file) ? SRC : ((index.isInLibrarySource(file) || index.isInLibraryClasses(file)) ? LIB : null);
386     }
387   }
388
389   private static class ScopeWithoutReferencesOnCompilation extends GlobalSearchScope {
390     private final TIntHashSet myReferentIds;
391
392     private ScopeWithoutReferencesOnCompilation(TIntHashSet ids) {
393       myReferentIds = ids;
394     }
395
396     @Override
397     public boolean contains(@NotNull VirtualFile file) {
398       return !(file instanceof VirtualFileWithId) || !myReferentIds.contains(((VirtualFileWithId)file).getId());
399     }
400
401     @Override
402     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
403       return 0;
404     }
405
406     @Override
407     public boolean isSearchInModuleContent(@NotNull Module aModule) {
408       return true;
409     }
410
411     @Override
412     public boolean isSearchInLibraries() {
413       return false;
414     }
415   }
416
417   @Override
418   public long getModificationCount() {
419     return myCompilationCount.longValue();
420   }
421
422   static class CompilerElementInfo {
423     final ElementPlace place;
424     final LightRef[] searchElements;
425
426     private CompilerElementInfo(ElementPlace place, LightRef... searchElements) {
427       this.place = place;
428       this.searchElements = searchElements;
429     }
430   }
431
432   private static class HierarchySearchKey {
433     private final CompilerHierarchySearchType mySearchType;
434     private final FileType mySearchFileType;
435
436     HierarchySearchKey(CompilerHierarchySearchType searchType, FileType searchFileType) {
437       mySearchType = searchType;
438       mySearchFileType = searchFileType;
439     }
440
441     @Override
442     public boolean equals(Object o) {
443       if (this == o) return true;
444       if (o == null || getClass() != o.getClass()) return false;
445
446       HierarchySearchKey key = (HierarchySearchKey)o;
447       return mySearchType == key.mySearchType && mySearchFileType == key.mySearchFileType;
448     }
449
450     @Override
451     public int hashCode() {
452       return 31 * mySearchType.hashCode() + mySearchFileType.hashCode();
453     }
454   }
455 }