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