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