2 * Copyright 2000-2016 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.
16 package com.intellij.compiler.backwardRefs;
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;
56 import java.util.Collections;
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;
66 import static com.intellij.psi.search.GlobalSearchScope.*;
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();
77 private volatile CompilerReferenceReader myReader;
79 public CompilerReferenceServiceImpl(Project project) {
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()));
90 public void projectOpened() {
92 myProject.getMessageBus().connect(myProject).subscribe(BuildManagerListener.TOPIC, new BuildManagerListener() {
94 public void buildStarted(Project project, UUID sessionId, boolean isAutomake) {
95 myDirtyModulesHolder.compilerActivityStarted();
100 CompilerManager compilerManager = CompilerManager.getInstance(myProject);
101 compilerManager.addCompilationStatusListener(new CompilationStatusListener() {
103 public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
104 compilationFinished(errors, compileContext);
108 public void automakeCompilationFinished(int errors, int warnings, CompileContext compileContext) {
109 compilationFinished(errors, compileContext);
112 private void compilationFinished(int errors, CompileContext context) {
113 BuildManager.getInstance().runCommand(() -> {
114 final Module[] compilationModules = context.getCompileScope().getAffectedModules();
115 Set<Module> modulesWithErrors;
117 modulesWithErrors = Stream.of(context.getMessages(CompilerMessageCategory.ERROR))
118 .map(CompilerMessage::getVirtualFile)
120 .map(f -> f == null ? null : myProjectFileIndex.getModuleForFile(f))
121 .collect(Collectors.toSet());
124 modulesWithErrors = Collections.emptySet();
126 if (modulesWithErrors.contains(null) /*unknown error location*/) {
127 myDirtyModulesHolder.compilerActivityFinished(Module.EMPTY_ARRAY, compilationModules);
129 myDirtyModulesHolder.compilerActivityFinished(compilationModules, modulesWithErrors.toArray(Module.EMPTY_ARRAY));
132 myCompilationCount.increment();
138 VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
140 public void fileCreated(@NotNull VirtualFileEvent event) {
141 processChange(event.getFile());
145 public void fileCopied(@NotNull VirtualFileCopyEvent event) {
146 processChange(event.getFile());
150 public void fileMoved(@NotNull VirtualFileMoveEvent event) {
151 processChange(event.getFile());
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());
162 public void beforeContentsChange(@NotNull VirtualFileEvent event) {
163 processChange(event.getFile());
167 public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
168 processChange(event.getFile());
172 public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
173 processChange(event.getFile());
176 private void processChange(VirtualFile file) {
177 myDirtyModulesHolder.fileChanged(file);
181 ProgressManager.getInstance().run(new Task.Backgroundable(myProject, CompilerBundle.message("compiler.ref.service.validation.task.name")) {
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(() -> {
189 myDirtyModulesHolder.compilerActivityFinished(projectCompileScope.getAffectedModules(), Module.EMPTY_ARRAY);
190 myCompilationCount.increment();
194 myDirtyModulesHolder.compilerActivityFinished(Module.EMPTY_ARRAY, projectCompileScope.getAffectedModules());
203 public void projectClosed() {
209 public GlobalSearchScope getScopeWithoutCodeReferences(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) {
210 if (!isServiceEnabledFor(element)) return null;
212 return CachedValuesManager.getCachedValue(element,
213 () -> CachedValueProvider.Result.create(new ConcurrentFactoryMap<CompilerSearchAdapter, GlobalSearchScope>() {
216 protected GlobalSearchScope create(CompilerSearchAdapter key) {
217 return calculateScopeWithoutReferences(element, key);
220 PsiModificationTracker.MODIFICATION_COUNT, this)).get(adapter);
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;
232 Couple<Map<VirtualFile, T[]>> directInheritorsAndCandidates =
233 CachedValuesManager.getCachedValue(aClass, () -> CachedValueProvider.Result.create(calculateDirectInheritors(aClass,
234 inheritorSearchAdapter,
237 PsiModificationTracker.MODIFICATION_COUNT,
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());
245 return new CompilerDirectInheritorInfoImpl<>(directInheritorsAndCandidates, dirtyScope, searchScope);
248 private boolean isServiceEnabledFor(PsiElement element) {
249 return isServiceEnabled() && !InjectedLanguageManager.getInstance(myProject).isInjectedFragment(ReadAction.compute(() -> element.getContainingFile()));
252 private boolean isServiceEnabled() {
253 return myReader != null && isEnabled();
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();
263 if (myReader == null) return null;
264 return myReader.getDirectInheritors(aClass,
268 myDirtyModulesHolder.getDirtyScope(),
272 myReadDataLock.unlock();
277 private GlobalSearchScope calculateScopeWithoutReferences(@NotNull PsiElement element, CompilerSearchAdapter adapter) {
278 TIntHashSet referentFileIds = getReferentFileIds(element, adapter);
279 if (referentFileIds == null) return null;
281 return getScopeRestrictedByFileTypes(new ScopeWithoutReferencesOnCompilation(referentFileIds).intersectWith(notScope(myDirtyModulesHolder.getDirtyScope())),
282 myFileTypes.toArray(new FileType[myFileTypes.size()]));
286 private TIntHashSet getReferentFileIds(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) {
287 final CompilerElementInfo compilerElementInfo = asCompilerElements(element, adapter, true);
288 if (compilerElementInfo == null) return null;
290 myReadDataLock.lock();
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());
299 return referentFileIds;
301 myReadDataLock.unlock();
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))) {
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);
323 return new CompilerElementInfo(place, compilerElement);
327 private void closeReaderIfNeed() {
328 myOpenCloseLock.lock();
330 if (myReader != null) {
335 myOpenCloseLock.unlock();
339 private void openReaderIfNeed() {
340 myOpenCloseLock.lock();
342 if (myProject.isOpen()) {
343 myReader = CompilerReferenceReader.create(myProject);
346 myOpenCloseLock.unlock();
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<>();
358 final VirtualFile vFile = fileIndex.findFileById(myProject, id);
359 assert vFile != null;
366 private enum ElementPlace {
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);
375 private static class ScopeWithoutReferencesOnCompilation extends GlobalSearchScope {
376 private final TIntHashSet myReferentIds;
378 private ScopeWithoutReferencesOnCompilation(TIntHashSet ids) {
383 public boolean contains(@NotNull VirtualFile file) {
384 return !(file instanceof VirtualFileWithId) || !myReferentIds.contains(((VirtualFileWithId)file).getId());
388 public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
393 public boolean isSearchInModuleContent(@NotNull Module aModule) {
398 public boolean isSearchInLibraries() {
404 public long getModificationCount() {
405 return myCompilationCount.longValue();
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;
413 private final Object myLock = new Object();
415 private void compilerActivityStarted() {
416 synchronized (myLock) {
417 myCompilationPhase = true;
421 private void compilerActivityFinished(Module[] affectedModules, Module[] markAsDirty) {
422 synchronized (myLock) {
423 myCompilationPhase = false;
425 ContainerUtil.removeAll(myChangedModules, affectedModules);
426 Collections.addAll(myChangedModules, markAsDirty);
427 myChangedModules.addAll(myChangedModulesDuringCompilation);
428 myChangedModulesDuringCompilation.clear();
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);
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);
450 myChangedModules.add(module);
457 private boolean contains(VirtualFile file) {
458 return getDirtyScope().contains(file);
462 static class CompilerElementInfo {
463 final ElementPlace place;
464 final CompilerElement[] searchElements;
466 private CompilerElementInfo(ElementPlace place, CompilerElement... searchElements) {
467 this.searchElements = searchElements;
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;
477 private CompilerDirectInheritorInfoImpl(Couple<Map<VirtualFile, T[]>> candidatePerFile,
478 GlobalSearchScope dirtyScope,
479 GlobalSearchScope searchScope) {
480 myCandidatePerFile = candidatePerFile;
481 myDirtyScope = dirtyScope;
482 mySearchScope = searchScope;
487 public Stream<T> getDirectInheritors() {
488 return selectClassesInScope(myCandidatePerFile.getFirst(), mySearchScope);
493 public Stream<T> getDirectInheritorCandidates() {
494 return selectClassesInScope(myCandidatePerFile.getSecond(), mySearchScope);
499 public GlobalSearchScope getDirtyScope() {
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()));