properly cache result for different adapters in CompilerReferenceServiceImpl (IDEA...
[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.CachedValueProvider;
31 import com.intellij.psi.util.CachedValuesManager;
32 import com.intellij.psi.util.ParameterizedCachedValue;
33 import com.intellij.psi.util.PsiModificationTracker;
34 import com.intellij.util.containers.ConcurrentFactoryMap;
35 import com.intellij.util.containers.ContainerUtil;
36 import com.intellij.util.indexing.FileBasedIndex;
37 import gnu.trove.THashSet;
38 import gnu.trove.TIntHashSet;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41 import org.jetbrains.annotations.TestOnly;
42
43 import java.util.Collections;
44 import java.util.Set;
45 import java.util.UUID;
46
47 import static com.intellij.psi.search.GlobalSearchScope.*;
48
49 public class CompilerReferenceServiceImpl extends CompilerReferenceService {
50   private static final Key<ParameterizedCachedValue<GlobalSearchScope, CompilerSearchAdapter>> CACHE_KEY = Key.create("compiler.ref.service.search");
51   private final ProjectFileIndex myProjectFileIndex;
52   private final Set<Module> myChangedModules = ContainerUtil.newConcurrentSet();
53   private final Set<FileType> myFileTypes;
54
55   private volatile CompilerReferenceReader myReader;
56   private volatile GlobalSearchScope myDirtyScope = EMPTY_SCOPE;
57
58   private final Object myLock = new Object();
59
60   public CompilerReferenceServiceImpl(Project project) {
61     super(project);
62     myProjectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
63     myFileTypes = Collections.unmodifiableSet(ContainerUtil.set(JavaFileType.INSTANCE));
64   }
65
66   @Override
67   public void projectOpened() {
68     if (isEnabled()) {
69       myProject.getMessageBus().connect(myProject).subscribe(BuildManagerListener.TOPIC, new BuildManagerListener() {
70         @Override
71         public void beforeBuildProcessStarted(Project project, UUID sessionId) {
72         }
73
74         @Override
75         public void buildStarted(Project project, UUID sessionId, boolean isAutomake) {
76           closeReaderIfNeed();
77         }
78
79         @Override
80         public void buildFinished(Project project, UUID sessionId, boolean isAutomake) {
81           myChangedModules.clear();
82           myDirtyScope = EMPTY_SCOPE;
83           openReaderIfNeed();
84         }
85       });
86
87       VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
88         @Override
89         public void fileCreated(@NotNull VirtualFileEvent event) {
90           processChange(event.getFile());
91         }
92
93         @Override
94         public void fileCopied(@NotNull VirtualFileCopyEvent event) {
95           processChange(event.getFile());
96         }
97
98         @Override
99         public void fileMoved(@NotNull VirtualFileMoveEvent event) {
100           processChange(event.getFile());
101         }
102
103         @Override
104         public void beforePropertyChange(@NotNull VirtualFilePropertyEvent event) {
105           if (VirtualFile.PROP_NAME.equals(event.getPropertyName()) || VirtualFile.PROP_SYMLINK_TARGET.equals(event.getPropertyName())) {
106             processChange(event.getFile());
107           }
108         }
109
110         @Override
111         public void beforeContentsChange(@NotNull VirtualFileEvent event) {
112           processChange(event.getFile());
113         }
114
115         @Override
116         public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
117           processChange(event.getFile());
118         }
119
120         @Override
121         public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
122           processChange(event.getFile());
123         }
124
125         private void processChange(VirtualFile file) {
126           if (myReader != null && myProjectFileIndex.isInSourceContent(file) && myFileTypes.contains(file.getFileType())) {
127             final Module module = myProjectFileIndex.getModuleForFile(file);
128             if (module != null) {
129               if (myChangedModules.add(module)) {
130                 myDirtyScope = myDirtyScope.union(module.getModuleWithDependentsScope());
131               }
132             }
133           }
134         }
135       }, myProject);
136     }
137
138   }
139
140   @Override
141   public void projectClosed() {
142     closeReaderIfNeed();
143   }
144
145   @Nullable
146   @Override
147   public GlobalSearchScope getScopeWithoutCodeReferences(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) {
148     if (!isServiceEnabled()) return null;
149
150     return CachedValuesManager.getCachedValue(element,
151                                               () -> CachedValueProvider.Result.create(new ConcurrentFactoryMap<CompilerSearchAdapter, GlobalSearchScope>() {
152                                                   @Nullable
153                                                   @Override
154                                                   protected GlobalSearchScope create(CompilerSearchAdapter key) {
155                                                     return calculateScopeWithoutReferences(element, key);
156                                                   }
157                                               },
158                                               PsiModificationTracker.MODIFICATION_COUNT)).get(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 }