use BuildManagerListener in CompilerReferenceServiceImpl to be notified about auto...
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / 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;
17
18 import com.intellij.compiler.server.BuildManagerListener;
19 import com.intellij.ide.highlighter.JavaFileType;
20 import com.intellij.openapi.fileTypes.FileType;
21 import com.intellij.openapi.module.Module;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.roots.ProjectFileIndex;
24 import com.intellij.openapi.roots.ProjectRootManager;
25 import com.intellij.openapi.util.Key;
26 import com.intellij.openapi.vfs.*;
27 import com.intellij.psi.PsiElement;
28 import com.intellij.psi.PsiFile;
29 import com.intellij.psi.search.GlobalSearchScope;
30 import com.intellij.psi.util.*;
31 import com.intellij.util.containers.ContainerUtil;
32 import com.intellij.util.indexing.FileBasedIndex;
33 import gnu.trove.THashSet;
34 import gnu.trove.TIntHashSet;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37 import org.jetbrains.annotations.TestOnly;
38
39 import java.util.Collections;
40 import java.util.Set;
41 import java.util.UUID;
42
43 import static com.intellij.psi.search.GlobalSearchScope.*;
44
45 public class CompilerReferenceServiceImpl extends CompilerReferenceService {
46   private static final Key<ParameterizedCachedValue<GlobalSearchScope, CompilerSearchAdapter>> CACHE_KEY = Key.create("compiler.ref.service.search");
47   private final ProjectFileIndex myProjectFileIndex;
48   private final Set<Module> myChangedModules = ContainerUtil.newConcurrentSet();
49   private final Set<FileType> myFileTypes;
50
51   private volatile CompilerReferenceReader myReader;
52   private volatile GlobalSearchScope myDirtyScope = EMPTY_SCOPE;
53
54   private final Object myLock = new Object();
55
56   public CompilerReferenceServiceImpl(Project project) {
57     super(project);
58     myProjectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
59     myFileTypes = Collections.unmodifiableSet(ContainerUtil.set(JavaFileType.INSTANCE));
60   }
61
62   @Override
63   public void projectOpened() {
64     if (isEnabled()) {
65       myProject.getMessageBus().connect(myProject).subscribe(BuildManagerListener.TOPIC, new BuildManagerListener() {
66         @Override
67         public void beforeBuildProcessStarted(Project project, UUID sessionId) {
68         }
69
70         @Override
71         public void buildStarted(Project project, UUID sessionId, boolean isAutomake) {
72           closeReaderIfNeed();
73         }
74
75         @Override
76         public void buildFinished(Project project, UUID sessionId, boolean isAutomake) {
77           myChangedModules.clear();
78           myDirtyScope = EMPTY_SCOPE;
79           openReaderIfNeed();
80         }
81       });
82
83       VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
84         @Override
85         public void fileCreated(@NotNull VirtualFileEvent event) {
86           processChange(event.getFile());
87         }
88
89         @Override
90         public void fileCopied(@NotNull VirtualFileCopyEvent event) {
91           processChange(event.getFile());
92         }
93
94         @Override
95         public void fileMoved(@NotNull VirtualFileMoveEvent event) {
96           processChange(event.getFile());
97         }
98
99         @Override
100         public void beforePropertyChange(@NotNull VirtualFilePropertyEvent event) {
101           if (VirtualFile.PROP_NAME.equals(event.getPropertyName()) || VirtualFile.PROP_SYMLINK_TARGET.equals(event.getPropertyName())) {
102             processChange(event.getFile());
103           }
104         }
105
106         @Override
107         public void beforeContentsChange(@NotNull VirtualFileEvent event) {
108           processChange(event.getFile());
109         }
110
111         @Override
112         public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
113           processChange(event.getFile());
114         }
115
116         @Override
117         public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
118           processChange(event.getFile());
119         }
120
121         private void processChange(VirtualFile file) {
122           if (myReader != null && myProjectFileIndex.isInSourceContent(file) && myFileTypes.contains(file.getFileType())) {
123             final Module module = myProjectFileIndex.getModuleForFile(file);
124             if (module != null) {
125               if (myChangedModules.add(module)) {
126                 myDirtyScope = myDirtyScope.union(module.getModuleWithDependentsScope());
127               }
128             }
129           }
130         }
131       }, myProject);
132     }
133
134   }
135
136   @Override
137   public void projectClosed() {
138     closeReaderIfNeed();
139   }
140
141   @Nullable
142   @Override
143   public GlobalSearchScope getScopeWithoutReferences(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) {
144     if (!isServiceEnabled()) return null;
145
146     final ParameterizedCachedValueProvider<GlobalSearchScope, CompilerSearchAdapter> cachedValueProvider =
147       new ParameterizedCachedValueProvider<GlobalSearchScope, CompilerSearchAdapter>() {
148         @Nullable
149         @Override
150         public CachedValueProvider.Result<GlobalSearchScope> compute(CompilerSearchAdapter param) {
151           return CachedValueProvider.Result.create(calculateScopeWithoutReferences(element, param), PsiModificationTracker.MODIFICATION_COUNT);
152         }
153       };
154     return CachedValuesManager.getManager(myProject).getParameterizedCachedValue(element,
155                                                                                  CACHE_KEY,
156                                                                                  cachedValueProvider,
157                                                                                  false,
158                                                                                  adapter);
159   }
160
161   private boolean isServiceEnabled() {
162     return myReader != null && isEnabled();
163   }
164
165   @Nullable
166   private GlobalSearchScope calculateScopeWithoutReferences(@NotNull PsiElement element, CompilerSearchAdapter adapter) {
167     TIntHashSet referentFileIds = getReferentFileIds(element, adapter);
168     if (referentFileIds == null) return null;
169
170     return getScopeRestrictedByFileTypes(new ScopeWithoutReferencesOnCompilation(referentFileIds).intersectWith(notScope(myDirtyScope)),
171                                          myFileTypes.toArray(new FileType[myFileTypes.size()]));
172   }
173
174   @Nullable
175   private TIntHashSet getReferentFileIds(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) {
176     final PsiFile file = element.getContainingFile();
177     if (file == null) return null;
178     final VirtualFile vFile = file.getVirtualFile();
179     if (vFile == null) return null;
180
181     ElementPlace place = ElementPlace.get(vFile, myProjectFileIndex);
182     if (place == null) {
183       return null;
184     }
185
186     if (myDirtyScope.contains(vFile)) {
187       return null;
188     }
189     CompilerElement[] compilerElements;
190     if (place == ElementPlace.SRC) {
191       final CompilerElement compilerElement = adapter.asCompilerElement(element);
192       compilerElements = compilerElement == null ? CompilerElement.EMPTY_ARRAY : new CompilerElement[]{compilerElement};
193     }
194     else {
195       compilerElements = adapter.libraryElementAsCompilerElements(element);
196     }
197     if (compilerElements.length == 0) return null;
198
199     synchronized (myLock) {
200       if (myReader == null) return null;
201       TIntHashSet referentFileIds = new TIntHashSet();
202       for (CompilerElement compilerElement : compilerElements) {
203         referentFileIds.addAll(myReader.findReferentFileIds(compilerElement, adapter).toArray());
204       }
205       return referentFileIds;
206     }
207   }
208
209   private void closeReaderIfNeed() {
210     synchronized (myLock) {
211       if (myReader != null) {
212         myReader.close();
213         myReader = null;
214       }
215     }
216   }
217
218   private void openReaderIfNeed() {
219     synchronized (myLock) {
220       if (myProject.isOpen()) {
221         myReader = CompilerReferenceReader.create(myProject);
222       }
223     }
224   }
225
226   @TestOnly
227   @Nullable
228   public Set<VirtualFile> getReferentFiles(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) {
229     FileBasedIndex fileIndex = FileBasedIndex.getInstance();
230     final TIntHashSet ids = getReferentFileIds(element, adapter);
231     if (ids == null) return null;
232     Set<VirtualFile> fileSet = new THashSet<>();
233     ids.forEach(id -> {
234       final VirtualFile vFile = fileIndex.findFileById(myProject, id);
235       assert vFile != null;
236       fileSet.add(vFile);
237       return true;
238     });
239     return fileSet;
240   }
241
242   private enum ElementPlace {
243     SRC, LIB;
244
245     private static ElementPlace get(VirtualFile file, ProjectFileIndex index) {
246       return index.isInSourceContent(file) ? SRC :
247              ((index.isInLibrarySource(file) || index.isInLibraryClasses(file)) ? LIB : null);
248     }
249   }
250
251   private static class ScopeWithoutReferencesOnCompilation extends GlobalSearchScope {
252     private final TIntHashSet myReferentIds;
253
254     private ScopeWithoutReferencesOnCompilation(TIntHashSet ids) {
255       myReferentIds = ids;
256     }
257
258     @Override
259     public boolean contains(@NotNull VirtualFile file) {
260       return !(file instanceof VirtualFileWithId) || !myReferentIds.contains(((VirtualFileWithId)file).getId());
261     }
262
263     @Override
264     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
265       return 0;
266     }
267
268     @Override
269     public boolean isSearchInModuleContent(@NotNull Module aModule) {
270       return true;
271     }
272
273     @Override
274     public boolean isSearchInLibraries() {
275       return false;
276     }
277   }
278 }