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