IDEA-168449 Change search scope for plugin descriptors
[idea/community.git] / platform / core-api / src / com / intellij / psi / search / GlobalSearchScope.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.psi.search;
17
18 import com.intellij.openapi.fileTypes.FileType;
19 import com.intellij.openapi.module.Module;
20 import com.intellij.openapi.module.UnloadedModuleDescription;
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.text.StringUtil;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.psi.PsiBundle;
27 import com.intellij.psi.PsiElement;
28 import com.intellij.psi.PsiFile;
29 import com.intellij.util.ArrayUtil;
30 import com.intellij.util.Processor;
31 import com.intellij.util.containers.ContainerUtil;
32 import org.jetbrains.annotations.Contract;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import java.util.*;
38
39 public abstract class GlobalSearchScope extends SearchScope implements ProjectAwareFileFilter {
40   @Nullable private final Project myProject;
41
42   protected GlobalSearchScope(@Nullable Project project) {
43     myProject = project;
44   }
45
46   protected GlobalSearchScope() {
47     this(null);
48   }
49
50   @Nullable
51   @Override
52   public Project getProject() {
53     return myProject;
54   }
55
56   /**
57    * @return a positive integer (+1), if file1 is located in the classpath before file2,
58    *         a negative integer (-1), if file1 is located in the classpath after file2
59    *         zero - otherwise or when the files are not comparable.
60    */
61   public abstract int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2);
62
63   // optimization methods:
64
65   public abstract boolean isSearchInModuleContent(@NotNull Module aModule);
66
67   public boolean isSearchInModuleContent(@NotNull Module aModule, boolean testSources) {
68     return isSearchInModuleContent(aModule);
69   }
70
71   @Override
72   public final boolean accept(VirtualFile file) {
73     return contains(file);
74   }
75
76   public abstract boolean isSearchInLibraries();
77
78   public boolean isForceSearchingInLibrarySources() {
79     return false;
80   }
81
82   public boolean isSearchOutsideRootModel() {
83     return false;
84   }
85
86   /**
87    * Returns descriptions of unloaded modules content of whose might be included into this scope if they had been loaded. Actually search in
88    * unloaded modules isn't performed, so this method is used to determine whether a warning about possible missing results should be shown.
89    */
90   public Collection<UnloadedModuleDescription> getUnloadedModulesBelongingToScope() {
91     return Collections.emptySet();
92   }
93
94   @NotNull
95   public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
96     if (scope == this) return this;
97     if (scope instanceof IntersectionScope && ((IntersectionScope)scope).containsScope(this)) {
98       return scope;
99     }
100     return new IntersectionScope(this, scope, null);
101   }
102
103   @NotNull
104   @Override
105   public SearchScope intersectWith(@NotNull SearchScope scope2) {
106     if (scope2 instanceof LocalSearchScope) {
107       LocalSearchScope localScope2 = (LocalSearchScope)scope2;
108       return intersectWith(localScope2);
109     }
110     return intersectWith((GlobalSearchScope)scope2);
111   }
112
113   @NotNull
114   public SearchScope intersectWith(@NotNull LocalSearchScope localScope2) {
115     PsiElement[] elements2 = localScope2.getScope();
116     List<PsiElement> result = new ArrayList<>(elements2.length);
117     for (final PsiElement element2 : elements2) {
118       if (PsiSearchScopeUtil.isInScope(this, element2)) {
119         result.add(element2);
120       }
121     }
122     return result.isEmpty() ? EMPTY_SCOPE : new LocalSearchScope(result.toArray(new PsiElement[result.size()]), null, localScope2.isIgnoreInjectedPsi());
123   }
124
125   @Override
126   @NotNull
127   public GlobalSearchScope union(@NotNull SearchScope scope) {
128     if (scope instanceof GlobalSearchScope) return uniteWith((GlobalSearchScope)scope);
129     return union((LocalSearchScope)scope);
130   }
131
132   @NotNull
133   public GlobalSearchScope union(@NotNull final LocalSearchScope scope) {
134     PsiElement[] localScopeElements = scope.getScope();
135     if (localScopeElements.length == 0) {
136       return this;
137     }
138     return new GlobalSearchScope(localScopeElements[0].getProject()) {
139       @Override
140       public boolean contains(@NotNull VirtualFile file) {
141         return GlobalSearchScope.this.contains(file) || scope.isInScope(file);
142       }
143
144       @Override
145       public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
146         return GlobalSearchScope.this.contains(file1) && GlobalSearchScope.this.contains(file2) ? GlobalSearchScope.this.compare(file1, file2) : 0;
147       }
148
149       @Override
150       public boolean isSearchInModuleContent(@NotNull Module aModule) {
151         return GlobalSearchScope.this.isSearchInModuleContent(aModule);
152       }
153
154       @Override
155       public boolean isSearchOutsideRootModel() {
156         return GlobalSearchScope.this.isSearchOutsideRootModel();
157       }
158
159       @Override
160       public boolean isSearchInLibraries() {
161         return GlobalSearchScope.this.isSearchInLibraries();
162       }
163
164       @Override
165       public Collection<UnloadedModuleDescription> getUnloadedModulesBelongingToScope() {
166         return GlobalSearchScope.this.getUnloadedModulesBelongingToScope();
167       }
168
169       @NonNls
170       @Override
171       public String toString() {
172         return "UnionToLocal: (" + GlobalSearchScope.this + ", " + scope + ")";
173       }
174     };
175   }
176
177   @NotNull
178   public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
179     if (scope == this) return scope;
180
181     return new UnionScope(this, scope);
182   }
183
184   @NotNull
185   @Contract(pure = true)
186   public static GlobalSearchScope union(@NotNull GlobalSearchScope[] scopes) {
187     if (scopes.length == 0) {
188       throw new IllegalArgumentException("Empty scope array");
189     }
190     if (scopes.length == 1) {
191       return scopes[0];
192     }
193     return new UnionScope(scopes);
194   }
195
196   @NotNull
197   public static GlobalSearchScope allScope(@NotNull Project project) {
198     return ProjectScope.getAllScope(project);
199   }
200
201   @NotNull
202   public static GlobalSearchScope projectScope(@NotNull Project project) {
203     return ProjectScope.getProjectScope(project);
204   }
205
206   @NotNull
207   public static GlobalSearchScope everythingScope(@NotNull Project project) {
208     return ProjectScope.getEverythingScope(project);
209   }
210
211   @NotNull
212   public static GlobalSearchScope notScope(@NotNull final GlobalSearchScope scope) {
213     return new NotScope(scope);
214   }
215
216   private static class NotScope extends DelegatingGlobalSearchScope {
217     private NotScope(@NotNull GlobalSearchScope scope) {
218       super(scope);
219     }
220
221     @Override
222     public boolean contains(@NotNull VirtualFile file) {
223       return !myBaseScope.contains(file);
224     }
225
226     @Override
227     public boolean isSearchInLibraries() {
228       return true; // not (in library A) is perfectly fine to find classes in another library B.
229     }
230
231     @Override
232     public boolean isSearchInModuleContent(@NotNull Module aModule, boolean testSources) {
233       return true; // not (some files in module A) is perfectly fine to find classes in another part of module A.
234     }
235
236     @Override
237     public boolean isSearchInModuleContent(@NotNull Module aModule) {
238       return true; // not (some files in module A) is perfectly fine to find classes in another part of module A.
239     }
240
241     @Override
242     public boolean isSearchOutsideRootModel() {
243       return true;
244     }
245
246     @Override
247     public String toString() {
248       return "NOT: "+myBaseScope;
249     }
250   }
251
252   /**
253    * Returns module scope including sources and tests, excluding libraries and dependencies.
254    *
255    * @param module the module to get the scope.
256    * @return scope including sources and tests, excluding libraries and dependencies.
257    */
258   @NotNull
259   public static GlobalSearchScope moduleScope(@NotNull Module module) {
260     return module.getModuleScope();
261   }
262
263   /**
264    * Returns module scope including sources, tests, and libraries, excluding dependencies.
265    *
266    * @param module the module to get the scope.
267    * @return scope including sources, tests, and libraries, excluding dependencies.
268    */
269   @NotNull
270   public static GlobalSearchScope moduleWithLibrariesScope(@NotNull Module module) {
271     return module.getModuleWithLibrariesScope();
272   }
273
274   /**
275    * Returns module scope including sources, tests, and dependencies, excluding libraries.
276    *
277    * @param module the module to get the scope.
278    * @return scope including sources, tests, and dependencies, excluding libraries.
279    */
280   @NotNull
281   public static GlobalSearchScope moduleWithDependenciesScope(@NotNull Module module) {
282     return module.getModuleWithDependenciesScope();
283   }
284
285   @NotNull
286   public static GlobalSearchScope moduleRuntimeScope(@NotNull Module module, final boolean includeTests) {
287     return module.getModuleRuntimeScope(includeTests);
288   }
289
290   @NotNull
291   public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module) {
292     return moduleWithDependenciesAndLibrariesScope(module, true);
293   }
294
295   @NotNull
296   public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module, boolean includeTests) {
297     return module.getModuleWithDependenciesAndLibrariesScope(includeTests);
298   }
299
300   @NotNull
301   public static GlobalSearchScope moduleWithDependentsScope(@NotNull Module module) {
302     return module.getModuleWithDependentsScope();
303   }
304
305   @NotNull
306   public static GlobalSearchScope moduleTestsWithDependentsScope(@NotNull Module module) {
307     return module.getModuleTestsWithDependentsScope();
308   }
309
310   @NotNull
311   public static GlobalSearchScope fileScope(@NotNull PsiFile psiFile) {
312     return new FileScope(psiFile.getProject(), psiFile.getVirtualFile());
313   }
314
315   @NotNull
316   public static GlobalSearchScope fileScope(@NotNull Project project, final VirtualFile virtualFile) {
317     return fileScope(project, virtualFile, null);
318   }
319
320   @NotNull
321   public static GlobalSearchScope fileScope(@NotNull Project project, @Nullable VirtualFile virtualFile, @Nullable final String displayName) {
322     return new FileScope(project, virtualFile) {
323       @NotNull
324       @Override
325       public String getDisplayName() {
326         return displayName == null ? super.getDisplayName() : displayName;
327       }
328     };
329   }
330
331   /**
332    * Please consider using {@link this#filesWithLibrariesScope} or {@link this#filesWithoutLibrariesScope} for optimization
333    */
334   @NotNull
335   public static GlobalSearchScope filesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
336     return filesScope(project, files, null);
337   }
338
339   /**
340    * Optimization. By default FilesScope makes a decision about searching in libraries by checking that
341    * at least one file is placed out of module roots. So if you're sure about files placement you can explicitly say FilesScope whether
342    * it should include libraries or not in order to avoid checking each file.
343    * Also, if you have a lot of files it might be faster to always search in libraries.
344    */
345   @NotNull
346   public static GlobalSearchScope filesWithoutLibrariesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
347     if (files.isEmpty()) return EMPTY_SCOPE;
348     return new FilesScope(project, files, false, false);
349   }
350
351   @NotNull
352   public static GlobalSearchScope filesWithLibrariesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
353     return filesWithLibrariesScope(project, files, false);
354   }
355
356   /**
357    * @since 2017.3
358    */
359   @NotNull
360   public static GlobalSearchScope filesWithLibrariesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files,
361                                                           boolean searchOutsideRootModel) {
362     if (files.isEmpty()) return EMPTY_SCOPE;
363     return new FilesScope(project, files, true, searchOutsideRootModel);
364   }
365
366   /**
367    * Please consider using {@link this#filesWithLibrariesScope} or {@link this#filesWithoutLibrariesScope} for optimization
368    */
369   @NotNull
370   public static GlobalSearchScope filesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files, @Nullable final String displayName) {
371     if (files.isEmpty()) return EMPTY_SCOPE;
372     return files.size() == 1? fileScope(project, files.iterator().next(), displayName) : new FilesScope(project, files) {
373       @NotNull
374       @Override
375       public String getDisplayName() {
376         return displayName == null ? super.getDisplayName() : displayName;
377       }
378     };
379   }
380
381   private static class IntersectionScope extends GlobalSearchScope {
382     private final GlobalSearchScope myScope1;
383     private final GlobalSearchScope myScope2;
384     private final String myDisplayName;
385
386     private IntersectionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2, String displayName) {
387       super(scope1.getProject() == null ? scope2.getProject() : scope1.getProject());
388       myScope1 = scope1;
389       myScope2 = scope2;
390       myDisplayName = displayName;
391     }
392
393     @NotNull
394     @Override
395     public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
396       return containsScope(scope) ? this : new IntersectionScope(this, scope, null);
397     }
398
399     private boolean containsScope(@NotNull GlobalSearchScope scope) {
400       if (myScope1.equals(scope) || myScope2.equals(scope) || equals(scope)) return true;
401       if (myScope1 instanceof IntersectionScope && ((IntersectionScope)myScope1).containsScope(scope)) return true;
402       if (myScope2 instanceof IntersectionScope && ((IntersectionScope)myScope2).containsScope(scope)) return true;
403       return false;
404     }
405
406     @NotNull
407     @Override
408     public String getDisplayName() {
409       if (myDisplayName == null) {
410         return PsiBundle.message("psi.search.scope.intersection", myScope1.getDisplayName(), myScope2.getDisplayName());
411       }
412       return myDisplayName;
413     }
414
415     @Override
416     public boolean contains(@NotNull VirtualFile file) {
417       return myScope1.contains(file) && myScope2.contains(file);
418     }
419
420     @Override
421     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
422       int res1 = myScope1.compare(file1, file2);
423       int res2 = myScope2.compare(file1, file2);
424
425       if (res1 == 0) return res2;
426       if (res2 == 0) return res1;
427
428       res1 /= Math.abs(res1);
429       res2 /= Math.abs(res2);
430       if (res1 == res2) return res1;
431
432       return 0;
433     }
434
435     @Override
436     public boolean isSearchInModuleContent(@NotNull Module aModule) {
437       return myScope1.isSearchInModuleContent(aModule) && myScope2.isSearchInModuleContent(aModule);
438     }
439
440     @Override
441     public boolean isSearchInModuleContent(@NotNull final Module aModule, final boolean testSources) {
442       return myScope1.isSearchInModuleContent(aModule, testSources) && myScope2.isSearchInModuleContent(aModule, testSources);
443     }
444
445     @Override
446     public boolean isSearchInLibraries() {
447       return myScope1.isSearchInLibraries() && myScope2.isSearchInLibraries();
448     }
449
450     @Override
451     public boolean isSearchOutsideRootModel() {
452       return myScope1.isSearchOutsideRootModel() && myScope2.isSearchOutsideRootModel();
453     }
454
455     @Override
456     public Collection<UnloadedModuleDescription> getUnloadedModulesBelongingToScope() {
457       return ContainerUtil.intersection(myScope1.getUnloadedModulesBelongingToScope(), myScope2.getUnloadedModulesBelongingToScope());
458     }
459
460     @Override
461     public boolean equals(Object o) {
462       if (this == o) return true;
463       if (!(o instanceof IntersectionScope)) return false;
464
465       IntersectionScope that = (IntersectionScope)o;
466
467       return myScope1.equals(that.myScope1) && myScope2.equals(that.myScope2);
468     }
469
470     @Override
471     public int hashCode() {
472       return 31 * myScope1.hashCode() + myScope2.hashCode();
473     }
474
475     @NonNls
476     @Override
477     public String toString() {
478       return "Intersection: (" + myScope1 + ", " + myScope2 + ")";
479     }
480   }
481
482   private static class UnionScope extends GlobalSearchScope {
483     private final GlobalSearchScope[] myScopes;
484     private final int myNestingLevel;
485
486     private UnionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2) {
487       this(new GlobalSearchScope[]{scope1, scope2});
488     }
489
490     private UnionScope(@NotNull GlobalSearchScope[] scopes) {
491       super(ContainerUtil.getFirstItem(ContainerUtil.mapNotNull(scopes, scope -> scope.getProject()), null));
492       if (scopes.length <= 1) throw new IllegalArgumentException("Too few scopes: "+ Arrays.asList(scopes));
493       myScopes = scopes;
494       final int[] nested = {0};
495       ContainerUtil.process(scopes, new Processor<GlobalSearchScope>() {
496         @Override
497         public boolean process(GlobalSearchScope scope) {
498           nested[0] = Math.max(nested[0], scope instanceof UnionScope ? ((UnionScope)scope).myNestingLevel : 0);
499           return true;
500         }
501       });
502       myNestingLevel = 1 + nested[0];
503       if (myNestingLevel > 1000) {
504         throw new IllegalStateException("Too many scopes combined: " + myNestingLevel + StringUtil.last(toString(), 500, true));
505       }
506     }
507
508     @NotNull
509     @Override
510     public String getDisplayName() {
511       return PsiBundle.message("psi.search.scope.union", myScopes[0].getDisplayName(), myScopes[1].getDisplayName());
512     }
513
514     @Override
515     public boolean contains(@NotNull final VirtualFile file) {
516       return ContainerUtil.find(myScopes, scope -> scope.contains(file)) != null;
517     }
518
519     @Override
520     public boolean isSearchOutsideRootModel() {
521       return ContainerUtil.find(myScopes, scope -> scope.isSearchOutsideRootModel()) != null;
522     }
523
524     @Override
525     public Collection<UnloadedModuleDescription> getUnloadedModulesBelongingToScope() {
526       Set<UnloadedModuleDescription> result = new LinkedHashSet<>();
527       for (GlobalSearchScope scope : myScopes) {
528         result.addAll(scope.getUnloadedModulesBelongingToScope());
529       }
530       return result;
531     }
532
533     @Override
534     public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
535       final int[] result = {0};
536       ContainerUtil.process(myScopes, scope -> {
537         int res1 = scope.contains(file1) && scope.contains(file2) ? scope.compare(file1, file2) : 0;
538         if (result[0] == 0) {
539           result[0] = res1;
540           return true;
541         }
542         if (result[0] > 0 != res1 > 0) {
543           result[0] = 0;
544           return false;
545         }
546         return true;
547       });
548       return result[0];
549     }
550
551     @Override
552     public boolean isSearchInModuleContent(@NotNull final Module module) {
553       return ContainerUtil.find(myScopes, scope -> scope.isSearchInModuleContent(module)) != null;
554     }
555
556     @Override
557     public boolean isSearchInModuleContent(@NotNull final Module module, final boolean testSources) {
558       return ContainerUtil.find(myScopes, scope -> scope.isSearchInModuleContent(module, testSources)) != null;
559     }
560
561     @Override
562     public boolean isSearchInLibraries() {
563       return ContainerUtil.find(myScopes, scope -> scope.isSearchInLibraries()) != null;
564     }
565
566     @Override
567     public boolean equals(Object o) {
568       if (this == o) return true;
569       if (!(o instanceof UnionScope)) return false;
570
571       UnionScope that = (UnionScope)o;
572
573       return new HashSet<>(Arrays.asList(myScopes)).equals(new HashSet<>(Arrays.asList(that.myScopes)));
574     }
575
576     @Override
577     public int hashCode() {
578       return Arrays.hashCode(myScopes);
579     }
580
581     @NonNls
582     @Override
583     public String toString() {
584       return "Union: (" + StringUtil.join(Arrays.asList(myScopes), ",") + ")";
585     }
586
587     @NotNull
588     @Override
589     public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
590       if (scope instanceof UnionScope) {
591         GlobalSearchScope[] newScopes = ArrayUtil.mergeArrays(myScopes, ((UnionScope)scope).myScopes);
592         return new UnionScope(newScopes);
593       }
594       return super.uniteWith(scope);
595     }
596   }
597
598   @NotNull
599   @Contract(pure=true)
600   public static GlobalSearchScope getScopeRestrictedByFileTypes(@NotNull GlobalSearchScope scope, @NotNull FileType... fileTypes) {
601     if (scope == EMPTY_SCOPE) {
602       return EMPTY_SCOPE;
603     }
604     if (fileTypes.length == 0) throw new IllegalArgumentException("empty fileTypes");
605     return new FileTypeRestrictionScope(scope, fileTypes);
606   }
607
608   private static class FileTypeRestrictionScope extends DelegatingGlobalSearchScope {
609     private final FileType[] myFileTypes;
610
611     private FileTypeRestrictionScope(@NotNull GlobalSearchScope scope, @NotNull FileType[] fileTypes) {
612       super(scope);
613       myFileTypes = fileTypes;
614     }
615
616     @Override
617     public boolean contains(@NotNull VirtualFile file) {
618       if (!super.contains(file)) return false;
619
620       final FileType fileType = file.getFileType();
621       for (FileType otherFileType : myFileTypes) {
622         if (fileType.equals(otherFileType)) return true;
623       }
624
625       return false;
626     }
627
628     @NotNull
629     @Override
630     public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
631       if (scope instanceof FileTypeRestrictionScope) {
632         FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope;
633         if (restrict.myBaseScope == myBaseScope) {
634           List<FileType> intersection = new ArrayList<>(Arrays.asList(restrict.myFileTypes));
635           intersection.retainAll(Arrays.asList(myFileTypes));
636           return new FileTypeRestrictionScope(myBaseScope, intersection.toArray(new FileType[intersection.size()]));
637         }
638       }
639       return super.intersectWith(scope);
640     }
641
642     @NotNull
643     @Override
644     public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
645       if (scope instanceof FileTypeRestrictionScope) {
646         FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope;
647         if (restrict.myBaseScope == myBaseScope) {
648           return new FileTypeRestrictionScope(myBaseScope, ArrayUtil.mergeArrays(myFileTypes, restrict.myFileTypes));
649         }
650       }
651       return super.uniteWith(scope);
652     }
653
654     @Override
655     public boolean equals(Object o) {
656       if (this == o) return true;
657       if (!(o instanceof FileTypeRestrictionScope)) return false;
658       if (!super.equals(o)) return false;
659
660       FileTypeRestrictionScope that = (FileTypeRestrictionScope)o;
661
662       return Arrays.equals(myFileTypes, that.myFileTypes);
663     }
664
665     @Override
666     public int hashCode() {
667       int result = super.hashCode();
668       result = 31 * result + Arrays.hashCode(myFileTypes);
669       return result;
670     }
671
672     @Override
673     public String toString() {
674       return "(restricted by file types: "+Arrays.asList(myFileTypes)+" in "+ myBaseScope + ")";
675     }
676   }
677
678   private static class EmptyScope extends GlobalSearchScope {
679     @Override
680     public boolean contains(@NotNull VirtualFile file) {
681       return false;
682     }
683
684     @Override
685     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
686       return 0;
687     }
688
689     @Override
690     public boolean isSearchInModuleContent(@NotNull Module aModule) {
691       return false;
692     }
693
694     @Override
695     public boolean isSearchInLibraries() {
696       return false;
697     }
698
699     @Override
700     @NotNull
701     public GlobalSearchScope intersectWith(@NotNull final GlobalSearchScope scope) {
702       return this;
703     }
704
705     @Override
706     @NotNull
707     public GlobalSearchScope uniteWith(@NotNull final GlobalSearchScope scope) {
708       return scope;
709     }
710
711     @Override
712     public String toString() {
713       return "EMPTY";
714     }
715   }
716
717   public static final GlobalSearchScope EMPTY_SCOPE = new EmptyScope();
718
719   private static class FileScope extends GlobalSearchScope implements Iterable<VirtualFile> {
720     private final VirtualFile myVirtualFile; // files can be out of project roots
721     private final Module myModule;
722     private final boolean mySearchOutsideContent;
723
724     private FileScope(@NotNull Project project, @Nullable VirtualFile virtualFile) {
725       super(project);
726       myVirtualFile = virtualFile;
727       final FileIndexFacade facade = FileIndexFacade.getInstance(project);
728       myModule = virtualFile == null || project.isDefault() ? null : facade.getModuleForFile(virtualFile);
729       mySearchOutsideContent = virtualFile != null && myModule == null && !facade.isInLibraryClasses(virtualFile) && !facade.isInLibrarySource(virtualFile);
730     }
731
732     @Override
733     public boolean contains(@NotNull VirtualFile file) {
734       return Comparing.equal(myVirtualFile, file);
735     }
736
737     @Override
738     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
739       return 0;
740     }
741
742     @Override
743     public boolean isSearchInModuleContent(@NotNull Module aModule) {
744       return aModule == myModule;
745     }
746
747     @Override
748     public boolean isSearchInLibraries() {
749       return myModule == null;
750     }
751
752     @Override
753     public String toString() {
754       return "File :"+myVirtualFile;
755     }
756
757     @NotNull
758     @Override
759     public Iterator<VirtualFile> iterator() {
760       return Collections.singletonList(myVirtualFile).iterator();
761     }
762
763     @Override
764     public boolean isSearchOutsideRootModel() {
765       return mySearchOutsideContent;
766     }
767   }
768
769   public static class FilesScope extends GlobalSearchScope implements Iterable<VirtualFile> {
770     private final Collection<VirtualFile> myFiles;
771     private final boolean mySearchOutsideRootModel;
772     private volatile Boolean myHasFilesOutOfProjectRoots;
773
774     /**
775      * @deprecated use {@link GlobalSearchScope#filesScope(Project, Collection)}
776      */
777     public FilesScope(@Nullable Project project, @NotNull Collection<VirtualFile> files) {
778       this(project, files, null, false);
779     }
780
781     // Optimization
782     private FilesScope(@Nullable  Project project, @NotNull Collection<VirtualFile> files, @Nullable Boolean hasFilesOutOfProjectRoots,
783                        boolean searchOutsideRootModel) {
784       super(project);
785       myFiles = files;
786       mySearchOutsideRootModel = searchOutsideRootModel;
787       myHasFilesOutOfProjectRoots = hasFilesOutOfProjectRoots;
788     }
789
790     @Override
791     public boolean contains(@NotNull final VirtualFile file) {
792       return myFiles.contains(file);
793     }
794
795     @Override
796     public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
797       return 0;
798     }
799
800     @Override
801     public boolean isSearchInModuleContent(@NotNull Module aModule) {
802       return true;
803     }
804
805     @Override
806     public boolean isSearchInLibraries() {
807       return hasFilesOutOfProjectRoots();
808     }
809
810     @Override
811     public boolean equals(Object o) {
812       return this == o || o instanceof FilesScope && myFiles.equals(((FilesScope)o).myFiles);
813     }
814
815     @Override
816     public int hashCode() {
817       return myFiles.hashCode();
818     }
819
820     private boolean hasFilesOutOfProjectRoots() {
821       Boolean result = myHasFilesOutOfProjectRoots;
822       if (result == null) {
823         Project project = getProject();
824         myHasFilesOutOfProjectRoots = result =
825           project != null && !project.isDefault() &&
826           myFiles.stream().anyMatch(file -> FileIndexFacade.getInstance(project).getModuleForFile(file) == null);
827       }
828       return result;
829     }
830
831     @Override
832     public String toString() {
833       List<VirtualFile> files = myFiles.size() <= 20 ? new ArrayList<>(myFiles) : new ArrayList<>(myFiles).subList(0, 20);
834       return "Files: ("+ files +"); search in libraries: " + (myHasFilesOutOfProjectRoots != null ? myHasFilesOutOfProjectRoots : "unknown");
835     }
836
837     @NotNull
838     @Override
839     public Iterator<VirtualFile> iterator() {
840       return myFiles.iterator();
841     }
842
843     @Override
844     public boolean isSearchOutsideRootModel() {
845       return mySearchOutsideRootModel;
846     }
847   }
848 }