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;
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;
43 import java.util.Collections;
45 import java.util.UUID;
47 import static com.intellij.psi.search.GlobalSearchScope.*;
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;
55 private volatile CompilerReferenceReader myReader;
56 private volatile GlobalSearchScope myDirtyScope = EMPTY_SCOPE;
58 private final Object myLock = new Object();
60 public CompilerReferenceServiceImpl(Project project) {
62 myProjectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
63 myFileTypes = Collections.unmodifiableSet(ContainerUtil.set(JavaFileType.INSTANCE));
67 public void projectOpened() {
69 myProject.getMessageBus().connect(myProject).subscribe(BuildManagerListener.TOPIC, new BuildManagerListener() {
71 public void beforeBuildProcessStarted(Project project, UUID sessionId) {
75 public void buildStarted(Project project, UUID sessionId, boolean isAutomake) {
80 public void buildFinished(Project project, UUID sessionId, boolean isAutomake) {
81 myChangedModules.clear();
82 myDirtyScope = EMPTY_SCOPE;
87 VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
89 public void fileCreated(@NotNull VirtualFileEvent event) {
90 processChange(event.getFile());
94 public void fileCopied(@NotNull VirtualFileCopyEvent event) {
95 processChange(event.getFile());
99 public void fileMoved(@NotNull VirtualFileMoveEvent event) {
100 processChange(event.getFile());
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());
111 public void beforeContentsChange(@NotNull VirtualFileEvent event) {
112 processChange(event.getFile());
116 public void beforeFileDeletion(@NotNull VirtualFileEvent event) {
117 processChange(event.getFile());
121 public void beforeFileMovement(@NotNull VirtualFileMoveEvent event) {
122 processChange(event.getFile());
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());
141 public void projectClosed() {
147 public GlobalSearchScope getScopeWithoutCodeReferences(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) {
148 if (!isServiceEnabled()) return null;
150 return CachedValuesManager.getCachedValue(element,
151 () -> CachedValueProvider.Result.create(new ConcurrentFactoryMap<CompilerSearchAdapter, GlobalSearchScope>() {
154 protected GlobalSearchScope create(CompilerSearchAdapter key) {
155 return calculateScopeWithoutReferences(element, key);
158 PsiModificationTracker.MODIFICATION_COUNT)).get(adapter);
161 private boolean isServiceEnabled() {
162 return myReader != null && isEnabled();
166 private GlobalSearchScope calculateScopeWithoutReferences(@NotNull PsiElement element, CompilerSearchAdapter adapter) {
167 TIntHashSet referentFileIds = getReferentFileIds(element, adapter);
168 if (referentFileIds == null) return null;
170 return getScopeRestrictedByFileTypes(new ScopeWithoutReferencesOnCompilation(referentFileIds).intersectWith(notScope(myDirtyScope)),
171 myFileTypes.toArray(new FileType[myFileTypes.size()]));
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;
181 ElementPlace place = ElementPlace.get(vFile, myProjectFileIndex);
186 if (myDirtyScope.contains(vFile)) {
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};
195 compilerElements = adapter.libraryElementAsCompilerElements(element);
197 if (compilerElements.length == 0) return null;
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());
205 return referentFileIds;
209 private void closeReaderIfNeed() {
210 synchronized (myLock) {
211 if (myReader != null) {
218 private void openReaderIfNeed() {
219 synchronized (myLock) {
220 if (myProject.isOpen()) {
221 myReader = CompilerReferenceReader.create(myProject);
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<>();
234 final VirtualFile vFile = fileIndex.findFileById(myProject, id);
235 assert vFile != null;
242 private enum ElementPlace {
245 private static ElementPlace get(VirtualFile file, ProjectFileIndex index) {
246 return index.isInSourceContent(file) ? SRC :
247 ((index.isInLibrarySource(file) || index.isInLibraryClasses(file)) ? LIB : null);
251 private static class ScopeWithoutReferencesOnCompilation extends GlobalSearchScope {
252 private final TIntHashSet myReferentIds;
254 private ScopeWithoutReferencesOnCompilation(TIntHashSet ids) {
259 public boolean contains(@NotNull VirtualFile file) {
260 return !(file instanceof VirtualFileWithId) || !myReferentIds.contains(((VirtualFileWithId)file).getId());
264 public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
269 public boolean isSearchInModuleContent(@NotNull Module aModule) {
274 public boolean isSearchInLibraries() {