2 * Copyright 2000-2015 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.psi.search;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.fileTypes.FileType;
20 import com.intellij.openapi.module.Module;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.roots.FileIndexFacade;
23 import com.intellij.openapi.util.Comparing;
24 import com.intellij.openapi.util.Condition;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.psi.PsiBundle;
28 import com.intellij.psi.PsiElement;
29 import com.intellij.psi.PsiFile;
30 import com.intellij.util.ArrayUtil;
31 import com.intellij.util.Function;
32 import com.intellij.util.Processor;
33 import com.intellij.util.containers.ContainerUtil;
34 import org.jetbrains.annotations.Contract;
35 import org.jetbrains.annotations.NonNls;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
41 public abstract class GlobalSearchScope extends SearchScope implements ProjectAwareFileFilter {
42 private static final Logger LOG = Logger.getInstance("#com.intellij.psi.search.GlobalSearchScope");
43 @Nullable private final Project myProject;
45 protected GlobalSearchScope(@Nullable Project project) {
49 protected GlobalSearchScope() {
53 public abstract boolean contains(@NotNull VirtualFile file);
57 public Project getProject() {
62 * @return a positive integer (+1), if file1 is located in the classpath before file2,
63 * a negative integer (-1), if file1 is located in the classpath after file2
64 * zero - otherwise or when the files are not comparable.
66 public abstract int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2);
68 // optimization methods:
70 public abstract boolean isSearchInModuleContent(@NotNull Module aModule);
72 public boolean isSearchInModuleContent(@NotNull Module aModule, boolean testSources) {
73 return isSearchInModuleContent(aModule);
77 public final boolean accept(VirtualFile file) {
78 return contains(file);
81 public abstract boolean isSearchInLibraries();
83 public boolean isForceSearchingInLibrarySources() {
87 public boolean isSearchOutsideRootModel() {
92 public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
93 if (scope == this) return this;
94 if (scope instanceof IntersectionScope) {
95 return scope.intersectWith(this);
97 return new IntersectionScope(this, scope, null);
102 public SearchScope intersectWith(@NotNull SearchScope scope2) {
103 if (scope2 instanceof LocalSearchScope) {
104 LocalSearchScope localScope2 = (LocalSearchScope)scope2;
105 return intersectWith(localScope2);
107 return intersectWith((GlobalSearchScope)scope2);
111 public SearchScope intersectWith(@NotNull LocalSearchScope localScope2) {
112 PsiElement[] elements2 = localScope2.getScope();
113 List<PsiElement> result = new ArrayList<PsiElement>(elements2.length);
114 for (final PsiElement element2 : elements2) {
115 if (PsiSearchScopeUtil.isInScope(this, element2)) {
116 result.add(element2);
119 return result.isEmpty() ? EMPTY_SCOPE : new LocalSearchScope(result.toArray(new PsiElement[result.size()]), null, localScope2.isIgnoreInjectedPsi());
124 public GlobalSearchScope union(@NotNull SearchScope scope) {
125 if (scope instanceof GlobalSearchScope) return uniteWith((GlobalSearchScope)scope);
126 return union((LocalSearchScope)scope);
130 public GlobalSearchScope union(@NotNull final LocalSearchScope scope) {
131 return new GlobalSearchScope(scope.getScope()[0].getProject()) {
133 public boolean contains(@NotNull VirtualFile file) {
134 return GlobalSearchScope.this.contains(file) || scope.isInScope(file);
138 public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
139 return GlobalSearchScope.this.contains(file1) && GlobalSearchScope.this.contains(file2) ? GlobalSearchScope.this.compare(file1, file2) : 0;
143 public boolean isSearchInModuleContent(@NotNull Module aModule) {
144 return GlobalSearchScope.this.isSearchInModuleContent(aModule);
148 public boolean isSearchOutsideRootModel() {
149 return GlobalSearchScope.this.isSearchOutsideRootModel();
153 public boolean isSearchInLibraries() {
154 return GlobalSearchScope.this.isSearchInLibraries();
159 public String toString() {
160 return "UnionToLocal: (" + GlobalSearchScope.this + ", " + scope + ")";
166 public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
167 if (scope == this) return scope;
169 return new UnionScope(this, scope);
173 @Contract(pure = true)
174 public static GlobalSearchScope union(@NotNull GlobalSearchScope[] scopes) {
175 if (scopes.length == 0) {
176 throw new IllegalArgumentException("Empty scope array");
178 if (scopes.length == 1) {
181 return new UnionScope(scopes);
185 public static GlobalSearchScope allScope(@NotNull Project project) {
186 return ProjectScope.getAllScope(project);
190 public static GlobalSearchScope projectScope(@NotNull Project project) {
191 return ProjectScope.getProjectScope(project);
195 public static GlobalSearchScope notScope(@NotNull final GlobalSearchScope scope) {
196 return new NotScope(scope);
198 private static class NotScope extends DelegatingGlobalSearchScope {
199 private NotScope(@NotNull GlobalSearchScope scope) {
204 public boolean contains(@NotNull VirtualFile file) {
205 return !myBaseScope.contains(file);
209 public boolean isSearchInLibraries() {
210 return true; // not (in library A) is perfectly fine to find classes in another library B.
214 public boolean isSearchInModuleContent(@NotNull Module aModule, boolean testSources) {
215 return true; // not (some files in module A) is perfectly fine to find classes in another part of module A.
219 public boolean isSearchInModuleContent(@NotNull Module aModule) {
220 return true; // not (some files in module A) is perfectly fine to find classes in another part of module A.
224 public boolean isSearchOutsideRootModel() {
229 public String toString() {
230 return "NOT: "+myBaseScope;
235 * Returns module scope including sources and tests, excluding libraries and dependencies.
237 * @param module the module to get the scope.
238 * @return scope including sources and tests, excluding libraries and dependencies.
241 public static GlobalSearchScope moduleScope(@NotNull Module module) {
242 return module.getModuleScope();
246 * Returns module scope including sources, tests, and libraries, excluding dependencies.
248 * @param module the module to get the scope.
249 * @return scope including sources, tests, and libraries, excluding dependencies.
252 public static GlobalSearchScope moduleWithLibrariesScope(@NotNull Module module) {
253 return module.getModuleWithLibrariesScope();
257 * Returns module scope including sources, tests, and dependencies, excluding libraries.
259 * @param module the module to get the scope.
260 * @return scope including sources, tests, and dependencies, excluding libraries.
263 public static GlobalSearchScope moduleWithDependenciesScope(@NotNull Module module) {
264 return module.getModuleWithDependenciesScope();
268 public static GlobalSearchScope moduleRuntimeScope(@NotNull Module module, final boolean includeTests) {
269 return module.getModuleRuntimeScope(includeTests);
273 public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module) {
274 return moduleWithDependenciesAndLibrariesScope(module, true);
278 public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module, boolean includeTests) {
279 return module.getModuleWithDependenciesAndLibrariesScope(includeTests);
283 public static GlobalSearchScope moduleWithDependentsScope(@NotNull Module module) {
284 return module.getModuleWithDependentsScope();
288 public static GlobalSearchScope moduleTestsWithDependentsScope(@NotNull Module module) {
289 return module.getModuleTestsWithDependentsScope();
293 public static GlobalSearchScope fileScope(@NotNull PsiFile psiFile) {
294 return new FileScope(psiFile.getProject(), psiFile.getVirtualFile());
298 public static GlobalSearchScope fileScope(@NotNull Project project, final VirtualFile virtualFile) {
299 return fileScope(project, virtualFile, null);
303 public static GlobalSearchScope fileScope(@NotNull Project project, final VirtualFile virtualFile, @Nullable final String displayName) {
304 return new FileScope(project, virtualFile) {
307 public String getDisplayName() {
308 return displayName == null ? super.getDisplayName() : displayName;
314 * Please consider using {@link this#filesWithLibrariesScope} or {@link this#filesWithoutLibrariesScope} for optimization
317 public static GlobalSearchScope filesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
318 return filesScope(project, files, null);
322 * Optimization. By default FilesScope makes a decision about searching in libraries by checking that
323 * at least one file is placed out of module roots. So if you're sure about files placement you can explicitly say FilesScope whether
324 * it should include libraries or not in order to avoid checking each file.
325 * Also, if you have a lot of files it might be faster to always search in libraries.
328 public static GlobalSearchScope filesWithoutLibrariesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
329 if (files.isEmpty()) return EMPTY_SCOPE;
330 return new FilesScope(project, files, false);
334 public static GlobalSearchScope filesWithLibrariesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
335 if (files.isEmpty()) return EMPTY_SCOPE;
336 return new FilesScope(project, files, true);
340 * Please consider using {@link this#filesWithLibrariesScope} or {@link this#filesWithoutLibrariesScope} for optimization
343 public static GlobalSearchScope filesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files, @Nullable final String displayName) {
344 if (files.isEmpty()) return EMPTY_SCOPE;
345 return files.size() == 1? fileScope(project, files.iterator().next(), displayName) : new FilesScope(project, files) {
348 public String getDisplayName() {
349 return displayName == null ? super.getDisplayName() : displayName;
354 private static class IntersectionScope extends GlobalSearchScope {
355 private final GlobalSearchScope myScope1;
356 private final GlobalSearchScope myScope2;
357 private final String myDisplayName;
359 private IntersectionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2, String displayName) {
360 super(scope1.getProject() == null ? scope2.getProject() : scope1.getProject());
363 myDisplayName = displayName;
368 public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
369 if (myScope1.equals(scope) || myScope2.equals(scope)) {
372 return new IntersectionScope(this, scope, null);
377 public String getDisplayName() {
378 if (myDisplayName == null) {
379 return PsiBundle.message("psi.search.scope.intersection", myScope1.getDisplayName(), myScope2.getDisplayName());
381 return myDisplayName;
385 public boolean contains(@NotNull VirtualFile file) {
386 return myScope1.contains(file) && myScope2.contains(file);
390 public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
391 int res1 = myScope1.compare(file1, file2);
392 int res2 = myScope2.compare(file1, file2);
394 if (res1 == 0) return res2;
395 if (res2 == 0) return res1;
397 res1 /= Math.abs(res1);
398 res2 /= Math.abs(res2);
399 if (res1 == res2) return res1;
405 public boolean isSearchInModuleContent(@NotNull Module aModule) {
406 return myScope1.isSearchInModuleContent(aModule) && myScope2.isSearchInModuleContent(aModule);
410 public boolean isSearchInModuleContent(@NotNull final Module aModule, final boolean testSources) {
411 return myScope1.isSearchInModuleContent(aModule, testSources) && myScope2.isSearchInModuleContent(aModule, testSources);
415 public boolean isSearchInLibraries() {
416 return myScope1.isSearchInLibraries() && myScope2.isSearchInLibraries();
420 public boolean isSearchOutsideRootModel() {
421 return myScope1.isSearchOutsideRootModel() && myScope2.isSearchOutsideRootModel();
425 public boolean equals(Object o) {
426 if (this == o) return true;
427 if (!(o instanceof IntersectionScope)) return false;
429 IntersectionScope that = (IntersectionScope)o;
431 return myScope1.equals(that.myScope1) && myScope2.equals(that.myScope2);
435 public int hashCode() {
436 return 31 * myScope1.hashCode() + myScope2.hashCode();
441 public String toString() {
442 return "Intersection: (" + myScope1 + ", " + myScope2 + ")";
446 private static class UnionScope extends GlobalSearchScope {
447 private final GlobalSearchScope[] myScopes;
448 private final int myNestingLevel;
450 private UnionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2) {
451 this(new GlobalSearchScope[]{scope1, scope2});
454 private UnionScope(@NotNull GlobalSearchScope[] scopes) {
455 super(ContainerUtil.getFirstItem(ContainerUtil.mapNotNull(scopes, new Function<GlobalSearchScope, Project>() {
457 public Project fun(GlobalSearchScope scope) {
458 return scope.getProject();
461 assert scopes.length > 1 : Arrays.asList(scopes);
463 final int[] nested = {0};
464 ContainerUtil.process(scopes, new Processor<GlobalSearchScope>() {
466 public boolean process(GlobalSearchScope scope) {
467 nested[0] = Math.max(nested[0], scope instanceof UnionScope ? ((UnionScope)scope).myNestingLevel : 0);
471 myNestingLevel = 1 + nested[0];
472 if (myNestingLevel > 1000) {
473 throw new IllegalStateException("Too many scopes combined: " + myNestingLevel + StringUtil.last(toString(), 500, true));
479 public String getDisplayName() {
480 return PsiBundle.message("psi.search.scope.union", myScopes[0].getDisplayName(), myScopes[1].getDisplayName());
484 public boolean contains(@NotNull final VirtualFile file) {
485 return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
487 public boolean value(GlobalSearchScope scope) {
488 return scope.contains(file);
494 public boolean isSearchOutsideRootModel() {
495 return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
497 public boolean value(GlobalSearchScope scope) {
498 return scope.isSearchOutsideRootModel();
504 public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
505 final int[] result = {0};
506 ContainerUtil.process(myScopes, new Processor<GlobalSearchScope>() {
508 public boolean process(GlobalSearchScope scope) {
509 int res1 = scope.contains(file1) && scope.contains(file2) ? scope.compare(file1, file2) : 0;
510 if (result[0] == 0) {
514 if ((result[0] > 0) != (res1 > 0)) {
525 public boolean isSearchInModuleContent(@NotNull final Module module) {
526 return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
528 public boolean value(GlobalSearchScope scope) {
529 return scope.isSearchInModuleContent(module);
535 public boolean isSearchInModuleContent(@NotNull final Module module, final boolean testSources) {
536 return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
538 public boolean value(GlobalSearchScope scope) {
539 return scope.isSearchInModuleContent(module, testSources);
545 public boolean isSearchInLibraries() {
546 return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
548 public boolean value(GlobalSearchScope scope) {
549 return scope.isSearchInLibraries();
555 public boolean equals(Object o) {
556 if (this == o) return true;
557 if (!(o instanceof UnionScope)) return false;
559 UnionScope that = (UnionScope)o;
561 return new HashSet<GlobalSearchScope>(Arrays.asList(myScopes)).equals(new HashSet<GlobalSearchScope>(Arrays.asList(that.myScopes)));
565 public int hashCode() {
566 return Arrays.hashCode(myScopes);
571 public String toString() {
572 return "Union: (" + StringUtil.join(Arrays.asList(myScopes), ",") + ")";
577 public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
578 if (scope instanceof UnionScope) {
579 GlobalSearchScope[] newScopes = ArrayUtil.mergeArrays(myScopes, ((UnionScope)scope).myScopes);
580 return new UnionScope(newScopes);
582 return super.uniteWith(scope);
587 public static GlobalSearchScope getScopeRestrictedByFileTypes(@NotNull GlobalSearchScope scope, @NotNull FileType... fileTypes) {
588 if (scope == EMPTY_SCOPE) {
591 LOG.assertTrue(fileTypes.length > 0);
592 return new FileTypeRestrictionScope(scope, fileTypes);
595 private static class FileTypeRestrictionScope extends DelegatingGlobalSearchScope {
596 private final FileType[] myFileTypes;
598 private FileTypeRestrictionScope(@NotNull GlobalSearchScope scope, @NotNull FileType[] fileTypes) {
600 myFileTypes = fileTypes;
604 public boolean contains(@NotNull VirtualFile file) {
605 if (!super.contains(file)) return false;
607 final FileType fileType = file.getFileType();
608 for (FileType otherFileType : myFileTypes) {
609 if (fileType.equals(otherFileType)) return true;
617 public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
618 if (scope instanceof FileTypeRestrictionScope) {
619 FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope;
620 if (restrict.myBaseScope == myBaseScope) {
621 List<FileType> intersection = new ArrayList<FileType>(Arrays.asList(restrict.myFileTypes));
622 intersection.retainAll(Arrays.asList(myFileTypes));
623 return new FileTypeRestrictionScope(myBaseScope, intersection.toArray(new FileType[intersection.size()]));
626 return super.intersectWith(scope);
631 public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
632 if (scope instanceof FileTypeRestrictionScope) {
633 FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope;
634 if (restrict.myBaseScope == myBaseScope) {
635 return new FileTypeRestrictionScope(myBaseScope, ArrayUtil.mergeArrays(myFileTypes, restrict.myFileTypes));
638 return super.uniteWith(scope);
642 public boolean equals(Object o) {
643 if (this == o) return true;
644 if (!(o instanceof FileTypeRestrictionScope)) return false;
645 if (!super.equals(o)) return false;
647 FileTypeRestrictionScope that = (FileTypeRestrictionScope)o;
649 return Arrays.equals(myFileTypes, that.myFileTypes);
653 public int hashCode() {
654 int result = super.hashCode();
655 result = 31 * result + Arrays.hashCode(myFileTypes);
660 public String toString() {
661 return "(" + myBaseScope + " restricted by file types: "+Arrays.asList(myFileTypes)+")";
665 private static class EmptyScope extends GlobalSearchScope {
667 public boolean contains(@NotNull VirtualFile file) {
672 public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
677 public boolean isSearchInModuleContent(@NotNull Module aModule) {
682 public boolean isSearchInLibraries() {
688 public GlobalSearchScope intersectWith(@NotNull final GlobalSearchScope scope) {
694 public GlobalSearchScope uniteWith(@NotNull final GlobalSearchScope scope) {
699 public String toString() {
704 public static final GlobalSearchScope EMPTY_SCOPE = new EmptyScope();
706 private static class FileScope extends GlobalSearchScope implements Iterable<VirtualFile> {
707 private final VirtualFile myVirtualFile; // files can be out of project roots
708 private final Module myModule;
709 private final boolean mySearchOutsideContent;
711 private FileScope(@NotNull Project project, VirtualFile virtualFile) {
713 myVirtualFile = virtualFile;
714 final FileIndexFacade facade = FileIndexFacade.getInstance(project);
715 myModule = virtualFile == null || project.isDefault() ? null : facade.getModuleForFile(virtualFile);
716 mySearchOutsideContent = virtualFile != null && myModule == null && !facade.isInLibraryClasses(virtualFile) && !facade.isInLibrarySource(virtualFile);
720 public boolean contains(@NotNull VirtualFile file) {
721 return Comparing.equal(myVirtualFile, file);
725 public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
730 public boolean isSearchInModuleContent(@NotNull Module aModule) {
731 return aModule == myModule;
735 public boolean isSearchInLibraries() {
736 return myModule == null;
740 public String toString() {
741 return "File :"+myVirtualFile;
745 public Iterator<VirtualFile> iterator() {
746 return Collections.singletonList(myVirtualFile).iterator();
750 public boolean isSearchOutsideRootModel() {
751 return mySearchOutsideContent;
755 public static class FilesScope extends GlobalSearchScope implements Iterable<VirtualFile> {
756 private final Collection<VirtualFile> myFiles;
757 private Boolean myHasFilesOutOfProjectRoots;
760 * @deprecated use {@link GlobalSearchScope#filesScope(Project, Collection)}
762 public FilesScope(@Nullable Project project, @NotNull Collection<VirtualFile> files) {
763 this(project, files, null);
767 private FilesScope(@Nullable Project project, @NotNull Collection<VirtualFile> files, @Nullable Boolean hasFilesOutOfProjectRoots) {
770 myHasFilesOutOfProjectRoots = hasFilesOutOfProjectRoots;
774 public boolean contains(@NotNull final VirtualFile file) {
775 return myFiles.contains(file);
779 public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
784 public boolean isSearchInModuleContent(@NotNull Module aModule) {
789 public boolean isSearchInLibraries() {
790 return hasFilesOutOfProjectRoots();
794 public boolean equals(Object o) {
795 return this == o || o instanceof FilesScope && myFiles.equals(((FilesScope)o).myFiles);
799 public int hashCode() {
800 return myFiles.hashCode();
803 private boolean hasFilesOutOfProjectRoots() {
804 if (myHasFilesOutOfProjectRoots == null) {
805 myHasFilesOutOfProjectRoots = false;
806 Project project = getProject();
807 if (project != null && !project.isDefault()) {
808 for (VirtualFile file : myFiles) {
809 if (FileIndexFacade.getInstance(project).getModuleForFile(file) == null) {
810 myHasFilesOutOfProjectRoots = true;
816 return myHasFilesOutOfProjectRoots;
820 public String toString() {
821 List<VirtualFile> files = myFiles.size() <= 20 ? new ArrayList<VirtualFile>(myFiles) : new ArrayList<VirtualFile>(myFiles).subList(0,20);
822 return "Files: ("+ files +"); search in libraries: " + (myHasFilesOutOfProjectRoots != null ? myHasFilesOutOfProjectRoots : "unknown");
826 public Iterator<VirtualFile> iterator() {
827 return myFiles.iterator();