| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.psi.search; |
| |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.FileIndexFacade; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiBundle; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.util.ArrayUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public abstract class GlobalSearchScope extends SearchScope implements ProjectAwareFileFilter { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.search.GlobalSearchScope"); |
| @Nullable private final Project myProject; |
| |
| protected GlobalSearchScope(@Nullable Project project) { |
| myProject = project; |
| } |
| |
| protected GlobalSearchScope() { |
| this(null); |
| } |
| |
| public abstract boolean contains(@NotNull VirtualFile file); |
| |
| @Nullable |
| @Override |
| public Project getProject() { |
| return myProject; |
| } |
| |
| /** |
| * @return a positive integer (+1), if file1 is located in the classpath before file2, |
| * a negative integer (-1), if file1 is located in the classpath after file2 |
| * zero - otherwise or when the file are not comparable. |
| */ |
| public abstract int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2); |
| |
| // optimization methods: |
| |
| public abstract boolean isSearchInModuleContent(@NotNull Module aModule); |
| |
| public boolean isSearchInModuleContent(@NotNull Module aModule, boolean testSources) { |
| return isSearchInModuleContent(aModule); |
| } |
| |
| @Override |
| public final boolean accept(VirtualFile file) { |
| return contains(file); |
| } |
| |
| public abstract boolean isSearchInLibraries(); |
| |
| public boolean isForceSearchingInLibrarySources() { |
| return false; |
| } |
| |
| public boolean isSearchOutsideRootModel() { |
| return false; |
| } |
| |
| @NotNull |
| public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) { |
| if (scope == this) return this; |
| if (scope instanceof IntersectionScope) { |
| return scope.intersectWith(this); |
| } |
| return new IntersectionScope(this, scope, null); |
| } |
| |
| @NotNull |
| @Override |
| public SearchScope intersectWith(@NotNull SearchScope scope2) { |
| if (scope2 instanceof LocalSearchScope) { |
| LocalSearchScope localScope2 = (LocalSearchScope)scope2; |
| return intersectWith(localScope2); |
| } |
| return intersectWith((GlobalSearchScope)scope2); |
| } |
| |
| @NotNull |
| public SearchScope intersectWith(@NotNull LocalSearchScope localScope2) { |
| PsiElement[] elements2 = localScope2.getScope(); |
| List<PsiElement> result = new ArrayList<PsiElement>(elements2.length); |
| for (final PsiElement element2 : elements2) { |
| if (PsiSearchScopeUtil.isInScope(this, element2)) { |
| result.add(element2); |
| } |
| } |
| return result.isEmpty() ? EMPTY_SCOPE : new LocalSearchScope(result.toArray(new PsiElement[result.size()]), null, localScope2.isIgnoreInjectedPsi()); |
| } |
| |
| @Override |
| @NotNull |
| public GlobalSearchScope union(@NotNull SearchScope scope) { |
| if (scope instanceof GlobalSearchScope) return uniteWith((GlobalSearchScope)scope); |
| return union((LocalSearchScope)scope); |
| } |
| |
| @NotNull |
| public GlobalSearchScope union(@NotNull final LocalSearchScope scope) { |
| return new GlobalSearchScope(scope.getScope()[0].getProject()) { |
| @Override |
| public boolean contains(@NotNull VirtualFile file) { |
| return GlobalSearchScope.this.contains(file) || scope.isInScope(file); |
| } |
| |
| @Override |
| public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { |
| return GlobalSearchScope.this.contains(file1) && GlobalSearchScope.this.contains(file2) ? GlobalSearchScope.this.compare(file1, file2) : 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return GlobalSearchScope.this.isSearchInModuleContent(aModule); |
| } |
| |
| @Override |
| public boolean isSearchOutsideRootModel() { |
| return GlobalSearchScope.this.isSearchOutsideRootModel(); |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return GlobalSearchScope.this.isSearchInLibraries(); |
| } |
| |
| @NonNls |
| @Override |
| public String toString() { |
| return "UnionToLocal: (" + GlobalSearchScope.this.toString() + ", " + scope + ")"; |
| } |
| }; |
| } |
| |
| @NotNull |
| public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) { |
| if (scope == this) return scope; |
| return new UnionScope(this, scope, null); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope allScope(@NotNull Project project) { |
| return ProjectScope.getAllScope(project); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope projectScope(@NotNull Project project) { |
| return ProjectScope.getProjectScope(project); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope notScope(@NotNull final GlobalSearchScope scope) { |
| return new DelegatingGlobalSearchScope(scope) { |
| @Override |
| public boolean contains(@NotNull final VirtualFile file) { |
| return !myBaseScope.contains(file); |
| } |
| |
| @Override |
| public boolean isSearchOutsideRootModel() { |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| return "NOT: "+myBaseScope; |
| } |
| }; |
| } |
| |
| /** |
| * Returns module scope including sources and tests, excluding libraries and dependencies. |
| * |
| * @param module the module to get the scope. |
| * @return scope including sources and tests, excluding libraries and dependencies. |
| */ |
| @NotNull |
| public static GlobalSearchScope moduleScope(@NotNull Module module) { |
| return module.getModuleScope(); |
| } |
| |
| /** |
| * Returns module scope including sources, tests, and libraries, excluding dependencies. |
| * |
| * @param module the module to get the scope. |
| * @return scope including sources, tests, and libraries, excluding dependencies. |
| */ |
| @NotNull |
| public static GlobalSearchScope moduleWithLibrariesScope(@NotNull Module module) { |
| return module.getModuleWithLibrariesScope(); |
| } |
| |
| /** |
| * Returns module scope including sources, tests, and dependencies, excluding libraries. |
| * |
| * @param module the module to get the scope. |
| * @return scope including sources, tests, and dependencies, excluding libraries. |
| */ |
| @NotNull |
| public static GlobalSearchScope moduleWithDependenciesScope(@NotNull Module module) { |
| return module.getModuleWithDependenciesScope(); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope moduleRuntimeScope(@NotNull Module module, final boolean includeTests) { |
| return module.getModuleRuntimeScope(includeTests); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module) { |
| return moduleWithDependenciesAndLibrariesScope(module, true); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module, boolean includeTests) { |
| return module.getModuleWithDependenciesAndLibrariesScope(includeTests); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope moduleWithDependentsScope(@NotNull Module module) { |
| return module.getModuleWithDependentsScope(); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope moduleTestsWithDependentsScope(@NotNull Module module) { |
| return module.getModuleTestsWithDependentsScope(); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope fileScope(@NotNull PsiFile psiFile) { |
| return new FileScope(psiFile.getProject(), psiFile.getVirtualFile()); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope fileScope(@NotNull Project project, final VirtualFile virtualFile) { |
| return fileScope(project, virtualFile, null); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope fileScope(@NotNull Project project, final VirtualFile virtualFile, @Nullable final String displayName) { |
| return new FileScope(project, virtualFile) { |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| return displayName == null ? super.getDisplayName() : displayName; |
| } |
| }; |
| } |
| |
| @NotNull |
| public static GlobalSearchScope filesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) { |
| return filesScope(project, files, null); |
| } |
| |
| @NotNull |
| public static GlobalSearchScope filesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files, @Nullable final String displayName) { |
| if (files.isEmpty()) return EMPTY_SCOPE; |
| return files.size() == 1? fileScope(project, files.iterator().next(), displayName) : new FilesScope(project, files) { |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| return displayName == null ? super.getDisplayName() : displayName; |
| } |
| }; |
| } |
| |
| static class IntersectionScope extends GlobalSearchScope { |
| private final GlobalSearchScope myScope1; |
| private final GlobalSearchScope myScope2; |
| private final String myDisplayName; |
| |
| IntersectionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2, String displayName) { |
| super(scope1.getProject() == null ? scope2.getProject() : scope1.getProject()); |
| myScope1 = scope1; |
| myScope2 = scope2; |
| myDisplayName = displayName; |
| } |
| |
| @NotNull |
| @Override |
| public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) { |
| if (myScope1.equals(scope) || myScope2.equals(scope)) { |
| return this; |
| } |
| return new IntersectionScope(this, scope, null); |
| } |
| |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| if (myDisplayName == null) { |
| return PsiBundle.message("psi.search.scope.intersection", myScope1.getDisplayName(), myScope2.getDisplayName()); |
| } |
| return myDisplayName; |
| } |
| |
| @Override |
| public boolean contains(@NotNull VirtualFile file) { |
| return myScope1.contains(file) && myScope2.contains(file); |
| } |
| |
| @Override |
| public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { |
| int res1 = myScope1.compare(file1, file2); |
| int res2 = myScope2.compare(file1, file2); |
| |
| if (res1 == 0) return res2; |
| if (res2 == 0) return res1; |
| |
| res1 /= Math.abs(res1); |
| res2 /= Math.abs(res2); |
| if (res1 == res2) return res1; |
| |
| return 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return myScope1.isSearchInModuleContent(aModule) && myScope2.isSearchInModuleContent(aModule); |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull final Module aModule, final boolean testSources) { |
| return myScope1.isSearchInModuleContent(aModule, testSources) && myScope2.isSearchInModuleContent(aModule, testSources); |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return myScope1.isSearchInLibraries() && myScope2.isSearchInLibraries(); |
| } |
| |
| @Override |
| public boolean isSearchOutsideRootModel() { |
| return myScope1.isSearchOutsideRootModel() && myScope2.isSearchOutsideRootModel(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof IntersectionScope)) return false; |
| |
| IntersectionScope that = (IntersectionScope)o; |
| |
| return myScope1.equals(that.myScope1) && myScope2.equals(that.myScope2); |
| } |
| |
| @Override |
| public int hashCode() { |
| return 31 * myScope1.hashCode() + myScope2.hashCode(); |
| } |
| |
| @NonNls |
| @Override |
| public String toString() { |
| return "Intersection: (" + myScope1 + ", " + myScope2 + ")"; |
| } |
| } |
| |
| private static class UnionScope extends GlobalSearchScope { |
| private final GlobalSearchScope myScope1; |
| private final GlobalSearchScope myScope2; |
| private final String myDisplayName; |
| |
| private UnionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2, String displayName) { |
| super(scope1.getProject() == null ? scope2.getProject() : scope1.getProject()); |
| myScope1 = scope1; |
| myScope2 = scope2; |
| myDisplayName = displayName; |
| } |
| |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| if (myDisplayName == null) { |
| return PsiBundle.message("psi.search.scope.union", myScope1.getDisplayName(), myScope2.getDisplayName()); |
| } |
| return myDisplayName; |
| } |
| |
| @Override |
| public boolean contains(@NotNull VirtualFile file) { |
| return myScope1.contains(file) || myScope2.contains(file); |
| } |
| |
| @Override |
| public boolean isSearchOutsideRootModel() { |
| return myScope1.isSearchOutsideRootModel() || myScope2.isSearchOutsideRootModel(); |
| } |
| |
| @Override |
| public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { |
| int res1 = myScope1.contains(file1) && myScope1.contains(file2) ? myScope1.compare(file1, file2) : 0; |
| int res2 = myScope2.contains(file1) && myScope2.contains(file2) ? myScope2.compare(file1, file2) : 0; |
| |
| if (res1 == 0) return res2; |
| if (res2 == 0) return res1; |
| |
| res1 /= Math.abs(res1); |
| res2 /= Math.abs(res2); |
| if (res1 == res2) return res1; |
| |
| return 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return myScope1.isSearchInModuleContent(aModule) || myScope2.isSearchInModuleContent(aModule); |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull final Module aModule, final boolean testSources) { |
| return myScope1.isSearchInModuleContent(aModule, testSources) || myScope2.isSearchInModuleContent(aModule, testSources); |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return myScope1.isSearchInLibraries() || myScope2.isSearchInLibraries(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof UnionScope)) return false; |
| |
| UnionScope that = (UnionScope)o; |
| |
| return myScope1.equals(that.myScope1) && myScope2.equals(that.myScope2); |
| } |
| |
| @Override |
| public int hashCode() { |
| return 31 * myScope1.hashCode() + myScope2.hashCode(); |
| } |
| |
| @NonNls |
| @Override |
| public String toString() { |
| return "Union: (" + myScope1 + ", " + myScope2 + ")"; |
| } |
| } |
| |
| @NotNull |
| public static GlobalSearchScope getScopeRestrictedByFileTypes(@NotNull GlobalSearchScope scope, @NotNull FileType... fileTypes) { |
| if (scope == EMPTY_SCOPE) { |
| return EMPTY_SCOPE; |
| } |
| LOG.assertTrue(fileTypes.length > 0); |
| return new FileTypeRestrictionScope(scope, fileTypes); |
| } |
| |
| private static class FileTypeRestrictionScope extends DelegatingGlobalSearchScope { |
| private final FileType[] myFileTypes; |
| |
| private FileTypeRestrictionScope(@NotNull GlobalSearchScope scope, @NotNull FileType[] fileTypes) { |
| super(scope); |
| myFileTypes = fileTypes; |
| } |
| |
| @Override |
| public boolean contains(@NotNull VirtualFile file) { |
| if (!super.contains(file)) return false; |
| |
| final FileType fileType = file.getFileType(); |
| for (FileType otherFileType : myFileTypes) { |
| if (fileType.equals(otherFileType)) return true; |
| } |
| |
| return false; |
| } |
| |
| @NotNull |
| @Override |
| public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) { |
| if (scope instanceof FileTypeRestrictionScope) { |
| FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope; |
| if (restrict.myBaseScope == myBaseScope) { |
| List<FileType> intersection = new ArrayList<FileType>(Arrays.asList(restrict.myFileTypes)); |
| intersection.retainAll(Arrays.asList(myFileTypes)); |
| return new FileTypeRestrictionScope(myBaseScope, intersection.toArray(new FileType[intersection.size()])); |
| } |
| } |
| return super.intersectWith(scope); |
| } |
| |
| @NotNull |
| @Override |
| public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) { |
| if (scope instanceof FileTypeRestrictionScope) { |
| FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope; |
| if (restrict.myBaseScope == myBaseScope) { |
| return new FileTypeRestrictionScope(myBaseScope, ArrayUtil.mergeArrays(myFileTypes, restrict.myFileTypes)); |
| } |
| } |
| return super.uniteWith(scope); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof FileTypeRestrictionScope)) return false; |
| if (!super.equals(o)) return false; |
| |
| FileTypeRestrictionScope that = (FileTypeRestrictionScope)o; |
| |
| return Arrays.equals(myFileTypes, that.myFileTypes); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = super.hashCode(); |
| result = 31 * result + Arrays.hashCode(myFileTypes); |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return "(" + myBaseScope + " restricted by file types: "+Arrays.asList(myFileTypes)+")"; |
| } |
| } |
| |
| private static class EmptyScope extends GlobalSearchScope { |
| @Override |
| public boolean contains(@NotNull VirtualFile file) { |
| return false; |
| } |
| |
| @Override |
| public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { |
| return 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return false; |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return false; |
| } |
| |
| @Override |
| @NotNull |
| public GlobalSearchScope intersectWith(@NotNull final GlobalSearchScope scope) { |
| return this; |
| } |
| |
| @Override |
| @NotNull |
| public GlobalSearchScope uniteWith(@NotNull final GlobalSearchScope scope) { |
| return scope; |
| } |
| |
| @Override |
| public String toString() { |
| return "EMPTY"; |
| } |
| } |
| |
| public static final GlobalSearchScope EMPTY_SCOPE = new EmptyScope(); |
| |
| private static class FileScope extends GlobalSearchScope implements Iterable<VirtualFile> { |
| private final VirtualFile myVirtualFile; // files can be out of project roots |
| private final Module myModule; |
| |
| private FileScope(@NotNull Project project, VirtualFile virtualFile) { |
| super(project); |
| myVirtualFile = virtualFile; |
| myModule = virtualFile == null || project.isDefault() ? null : FileIndexFacade.getInstance(project).getModuleForFile(virtualFile); |
| } |
| |
| @Override |
| public boolean contains(@NotNull VirtualFile file) { |
| return Comparing.equal(myVirtualFile, file); |
| } |
| |
| @Override |
| public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) { |
| return 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return aModule == myModule; |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return myModule == null; |
| } |
| |
| @Override |
| public String toString() { |
| return "File :"+myVirtualFile; |
| } |
| |
| @Override |
| public Iterator<VirtualFile> iterator() { |
| return Collections.singletonList(myVirtualFile).iterator(); |
| } |
| } |
| |
| public static class FilesScope extends GlobalSearchScope implements Iterable<VirtualFile> { |
| private final Collection<VirtualFile> myFiles; // files can be out of project roots |
| |
| public FilesScope(final Project project, @NotNull Collection<VirtualFile> files) { |
| super(project); |
| myFiles = files; |
| } |
| |
| @Override |
| public boolean contains(@NotNull final VirtualFile file) { |
| return myFiles.contains(file); |
| } |
| |
| @Override |
| public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) { |
| return 0; |
| } |
| |
| @Override |
| public boolean isSearchInModuleContent(@NotNull Module aModule) { |
| return true; |
| } |
| |
| @Override |
| public boolean isSearchInLibraries() { |
| return false; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| return o instanceof FilesScope && myFiles.equals(((FilesScope)o).myFiles); |
| } |
| |
| @Override |
| public int hashCode() { |
| return myFiles.hashCode(); |
| } |
| |
| @Override |
| public String toString() { |
| List<VirtualFile> files = myFiles.size() <= 20 ? new ArrayList<VirtualFile>(myFiles) : new ArrayList<VirtualFile>(myFiles).subList(0,20); |
| return "Files: ("+ files +")"; |
| } |
| |
| @Override |
| public Iterator<VirtualFile> iterator() { |
| return myFiles.iterator(); |
| } |
| } |
| } |