scope: display "Open Files" label in scope selector combobox instead of bare hashCode...
[idea/community.git] / platform / core-api / src / com / intellij / psi / search / GlobalSearchScope.java
1 /*
2  * Copyright 2000-2015 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.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;
38
39 import java.util.*;
40
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;
44
45   protected GlobalSearchScope(@Nullable Project project) {
46     myProject = project;
47   }
48
49   protected GlobalSearchScope() {
50     this(null);
51   }
52
53   public abstract boolean contains(@NotNull VirtualFile file);
54
55   @Nullable
56   @Override
57   public Project getProject() {
58     return myProject;
59   }
60
61   /**
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.
65    */
66   public abstract int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2);
67
68   // optimization methods:
69
70   public abstract boolean isSearchInModuleContent(@NotNull Module aModule);
71
72   public boolean isSearchInModuleContent(@NotNull Module aModule, boolean testSources) {
73     return isSearchInModuleContent(aModule);
74   }
75
76   @Override
77   public final boolean accept(VirtualFile file) {
78     return contains(file);
79   }
80
81   public abstract boolean isSearchInLibraries();
82
83   public boolean isForceSearchingInLibrarySources() {
84     return false;
85   }
86
87   public boolean isSearchOutsideRootModel() {
88     return false;
89   }
90
91   @NotNull
92   public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
93     if (scope == this) return this;
94     if (scope instanceof IntersectionScope) {
95       return scope.intersectWith(this);
96     }
97     return new IntersectionScope(this, scope, null);
98   }
99
100   @NotNull
101   @Override
102   public SearchScope intersectWith(@NotNull SearchScope scope2) {
103     if (scope2 instanceof LocalSearchScope) {
104       LocalSearchScope localScope2 = (LocalSearchScope)scope2;
105       return intersectWith(localScope2);
106     }
107     return intersectWith((GlobalSearchScope)scope2);
108   }
109
110   @NotNull
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);
117       }
118     }
119     return result.isEmpty() ? EMPTY_SCOPE : new LocalSearchScope(result.toArray(new PsiElement[result.size()]), null, localScope2.isIgnoreInjectedPsi());
120   }
121
122   @Override
123   @NotNull
124   public GlobalSearchScope union(@NotNull SearchScope scope) {
125     if (scope instanceof GlobalSearchScope) return uniteWith((GlobalSearchScope)scope);
126     return union((LocalSearchScope)scope);
127   }
128
129   @NotNull
130   public GlobalSearchScope union(@NotNull final LocalSearchScope scope) {
131     return new GlobalSearchScope(scope.getScope()[0].getProject()) {
132       @Override
133       public boolean contains(@NotNull VirtualFile file) {
134         return GlobalSearchScope.this.contains(file) || scope.isInScope(file);
135       }
136
137       @Override
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;
140       }
141
142       @Override
143       public boolean isSearchInModuleContent(@NotNull Module aModule) {
144         return GlobalSearchScope.this.isSearchInModuleContent(aModule);
145       }
146
147       @Override
148       public boolean isSearchOutsideRootModel() {
149         return GlobalSearchScope.this.isSearchOutsideRootModel();
150       }
151
152       @Override
153       public boolean isSearchInLibraries() {
154         return GlobalSearchScope.this.isSearchInLibraries();
155       }
156
157       @NonNls
158       @Override
159       public String toString() {
160         return "UnionToLocal: (" + GlobalSearchScope.this + ", " + scope + ")";
161       }
162     };
163   }
164
165   @NotNull
166   public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
167     if (scope == this) return scope;
168
169     return new UnionScope(this, scope);
170   }
171
172   @NotNull
173   @Contract(pure = true)
174   public static GlobalSearchScope union(@NotNull GlobalSearchScope[] scopes) {
175     if (scopes.length == 0) {
176       throw new IllegalArgumentException("Empty scope array");
177     }
178     if (scopes.length == 1) {
179       return scopes[0];
180     }
181     return new UnionScope(scopes);
182   }
183
184   @NotNull
185   public static GlobalSearchScope allScope(@NotNull Project project) {
186     return ProjectScope.getAllScope(project);
187   }
188
189   @NotNull
190   public static GlobalSearchScope projectScope(@NotNull Project project) {
191     return ProjectScope.getProjectScope(project);
192   }
193
194   @NotNull
195   public static GlobalSearchScope notScope(@NotNull final GlobalSearchScope scope) {
196     return new NotScope(scope);
197   }
198   private static class NotScope extends DelegatingGlobalSearchScope {
199     private NotScope(@NotNull GlobalSearchScope scope) {
200       super(scope);
201     }
202
203     @Override
204     public boolean contains(@NotNull VirtualFile file) {
205       return !myBaseScope.contains(file);
206     }
207
208     @Override
209     public boolean isSearchInLibraries() {
210       return true; // not (in library A) is perfectly fine to find classes in another library B.
211     }
212
213     @Override
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.
216     }
217
218     @Override
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.
221     }
222
223     @Override
224     public boolean isSearchOutsideRootModel() {
225       return true;
226     }
227
228     @Override
229     public String toString() {
230       return "NOT: "+myBaseScope;
231     }
232   }
233
234   /**
235    * Returns module scope including sources and tests, excluding libraries and dependencies.
236    *
237    * @param module the module to get the scope.
238    * @return scope including sources and tests, excluding libraries and dependencies.
239    */
240   @NotNull
241   public static GlobalSearchScope moduleScope(@NotNull Module module) {
242     return module.getModuleScope();
243   }
244
245   /**
246    * Returns module scope including sources, tests, and libraries, excluding dependencies.
247    *
248    * @param module the module to get the scope.
249    * @return scope including sources, tests, and libraries, excluding dependencies.
250    */
251   @NotNull
252   public static GlobalSearchScope moduleWithLibrariesScope(@NotNull Module module) {
253     return module.getModuleWithLibrariesScope();
254   }
255
256   /**
257    * Returns module scope including sources, tests, and dependencies, excluding libraries.
258    *
259    * @param module the module to get the scope.
260    * @return scope including sources, tests, and dependencies, excluding libraries.
261    */
262   @NotNull
263   public static GlobalSearchScope moduleWithDependenciesScope(@NotNull Module module) {
264     return module.getModuleWithDependenciesScope();
265   }
266
267   @NotNull
268   public static GlobalSearchScope moduleRuntimeScope(@NotNull Module module, final boolean includeTests) {
269     return module.getModuleRuntimeScope(includeTests);
270   }
271
272   @NotNull
273   public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module) {
274     return moduleWithDependenciesAndLibrariesScope(module, true);
275   }
276
277   @NotNull
278   public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module, boolean includeTests) {
279     return module.getModuleWithDependenciesAndLibrariesScope(includeTests);
280   }
281
282   @NotNull
283   public static GlobalSearchScope moduleWithDependentsScope(@NotNull Module module) {
284     return module.getModuleWithDependentsScope();
285   }
286
287   @NotNull
288   public static GlobalSearchScope moduleTestsWithDependentsScope(@NotNull Module module) {
289     return module.getModuleTestsWithDependentsScope();
290   }
291
292   @NotNull
293   public static GlobalSearchScope fileScope(@NotNull PsiFile psiFile) {
294     return new FileScope(psiFile.getProject(), psiFile.getVirtualFile());
295   }
296
297   @NotNull
298   public static GlobalSearchScope fileScope(@NotNull Project project, final VirtualFile virtualFile) {
299     return fileScope(project, virtualFile, null);
300   }
301
302   @NotNull
303   public static GlobalSearchScope fileScope(@NotNull Project project, @Nullable VirtualFile virtualFile, @Nullable final String displayName) {
304     return new FileScope(project, virtualFile) {
305       @NotNull
306       @Override
307       public String getDisplayName() {
308         return displayName == null ? super.getDisplayName() : displayName;
309       }
310     };
311   }
312
313   /**
314    * Please consider using {@link this#filesWithLibrariesScope} or {@link this#filesWithoutLibrariesScope} for optimization
315    */
316   @NotNull
317   public static GlobalSearchScope filesScope(@NotNull Project project, @NotNull Collection<VirtualFile> files) {
318     return filesScope(project, files, null);
319   }
320
321   /**
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.
326    */
327   @NotNull
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);
331   }
332   
333   @NotNull
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);
337   }
338
339   /**
340    * Please consider using {@link this#filesWithLibrariesScope} or {@link this#filesWithoutLibrariesScope} for optimization
341    */
342   @NotNull
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) {
346       @NotNull
347       @Override
348       public String getDisplayName() {
349         return displayName == null ? super.getDisplayName() : displayName;
350       }
351     };
352   }
353
354   private static class IntersectionScope extends GlobalSearchScope {
355     private final GlobalSearchScope myScope1;
356     private final GlobalSearchScope myScope2;
357     private final String myDisplayName;
358
359     private IntersectionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2, String displayName) {
360       super(scope1.getProject() == null ? scope2.getProject() : scope1.getProject());
361       myScope1 = scope1;
362       myScope2 = scope2;
363       myDisplayName = displayName;
364     }
365
366     @NotNull
367     @Override
368     public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
369       if (myScope1.equals(scope) || myScope2.equals(scope)) {
370         return this;
371       }
372       return new IntersectionScope(this, scope, null);
373     }
374
375     @NotNull
376     @Override
377     public String getDisplayName() {
378       if (myDisplayName == null) {
379         return PsiBundle.message("psi.search.scope.intersection", myScope1.getDisplayName(), myScope2.getDisplayName());
380       }
381       return myDisplayName;
382     }
383
384     @Override
385     public boolean contains(@NotNull VirtualFile file) {
386       return myScope1.contains(file) && myScope2.contains(file);
387     }
388
389     @Override
390     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
391       int res1 = myScope1.compare(file1, file2);
392       int res2 = myScope2.compare(file1, file2);
393
394       if (res1 == 0) return res2;
395       if (res2 == 0) return res1;
396
397       res1 /= Math.abs(res1);
398       res2 /= Math.abs(res2);
399       if (res1 == res2) return res1;
400
401       return 0;
402     }
403
404     @Override
405     public boolean isSearchInModuleContent(@NotNull Module aModule) {
406       return myScope1.isSearchInModuleContent(aModule) && myScope2.isSearchInModuleContent(aModule);
407     }
408
409     @Override
410     public boolean isSearchInModuleContent(@NotNull final Module aModule, final boolean testSources) {
411       return myScope1.isSearchInModuleContent(aModule, testSources) && myScope2.isSearchInModuleContent(aModule, testSources);
412     }
413
414     @Override
415     public boolean isSearchInLibraries() {
416       return myScope1.isSearchInLibraries() && myScope2.isSearchInLibraries();
417     }
418
419     @Override
420     public boolean isSearchOutsideRootModel() {
421       return myScope1.isSearchOutsideRootModel() && myScope2.isSearchOutsideRootModel();
422     }
423
424     @Override
425     public boolean equals(Object o) {
426       if (this == o) return true;
427       if (!(o instanceof IntersectionScope)) return false;
428
429       IntersectionScope that = (IntersectionScope)o;
430
431       return myScope1.equals(that.myScope1) && myScope2.equals(that.myScope2);
432     }
433
434     @Override
435     public int hashCode() {
436       return 31 * myScope1.hashCode() + myScope2.hashCode();
437     }
438
439     @NonNls
440     @Override
441     public String toString() {
442       return "Intersection: (" + myScope1 + ", " + myScope2 + ")";
443     }
444   }
445
446   private static class UnionScope extends GlobalSearchScope {
447     private final GlobalSearchScope[] myScopes;
448     private final int myNestingLevel;
449
450     private UnionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2) {
451       this(new GlobalSearchScope[]{scope1, scope2});
452     }
453
454     private UnionScope(@NotNull GlobalSearchScope[] scopes) {
455       super(ContainerUtil.getFirstItem(ContainerUtil.mapNotNull(scopes, new Function<GlobalSearchScope, Project>() {
456         @Override
457         public Project fun(GlobalSearchScope scope) {
458           return scope.getProject();
459         }
460       }), null));
461       assert scopes.length > 1 : Arrays.asList(scopes);
462       myScopes = scopes;
463       final int[] nested = {0};
464       ContainerUtil.process(scopes, new Processor<GlobalSearchScope>() {
465         @Override
466         public boolean process(GlobalSearchScope scope) {
467           nested[0] = Math.max(nested[0], scope instanceof UnionScope ? ((UnionScope)scope).myNestingLevel : 0);
468           return true;
469         }
470       });
471       myNestingLevel = 1 + nested[0];
472       if (myNestingLevel > 1000) {
473         throw new IllegalStateException("Too many scopes combined: " + myNestingLevel + StringUtil.last(toString(), 500, true));
474       }
475     }
476
477     @NotNull
478     @Override
479     public String getDisplayName() {
480       return PsiBundle.message("psi.search.scope.union", myScopes[0].getDisplayName(), myScopes[1].getDisplayName());
481     }
482
483     @Override
484     public boolean contains(@NotNull final VirtualFile file) {
485       return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
486         @Override
487         public boolean value(GlobalSearchScope scope) {
488           return scope.contains(file);
489         }
490       }) != null;
491     }
492
493     @Override
494     public boolean isSearchOutsideRootModel() {
495       return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
496         @Override
497         public boolean value(GlobalSearchScope scope) {
498           return scope.isSearchOutsideRootModel();
499         }
500       }) != null;
501     }
502
503     @Override
504     public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
505       final int[] result = {0};
506       ContainerUtil.process(myScopes, new Processor<GlobalSearchScope>() {
507         @Override
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) {
511             result[0] = res1;
512             return true;
513           }
514           if ((result[0] > 0) != (res1 > 0)) {
515             result[0] = 0;
516             return false;
517           }
518           return true;
519         }
520       });
521       return result[0];
522     }
523
524     @Override
525     public boolean isSearchInModuleContent(@NotNull final Module module) {
526       return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
527         @Override
528         public boolean value(GlobalSearchScope scope) {
529           return scope.isSearchInModuleContent(module);
530         }
531       }) != null;
532     }
533
534     @Override
535     public boolean isSearchInModuleContent(@NotNull final Module module, final boolean testSources) {
536       return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
537         @Override
538         public boolean value(GlobalSearchScope scope) {
539           return scope.isSearchInModuleContent(module, testSources);
540         }
541       }) != null;
542     }
543
544     @Override
545     public boolean isSearchInLibraries() {
546       return ContainerUtil.find(myScopes, new Condition<GlobalSearchScope>() {
547         @Override
548         public boolean value(GlobalSearchScope scope) {
549           return scope.isSearchInLibraries();
550         }
551       }) != null;
552     }
553
554     @Override
555     public boolean equals(Object o) {
556       if (this == o) return true;
557       if (!(o instanceof UnionScope)) return false;
558
559       UnionScope that = (UnionScope)o;
560
561       return new HashSet<GlobalSearchScope>(Arrays.asList(myScopes)).equals(new HashSet<GlobalSearchScope>(Arrays.asList(that.myScopes)));
562     }
563
564     @Override
565     public int hashCode() {
566       return Arrays.hashCode(myScopes);
567     }
568
569     @NonNls
570     @Override
571     public String toString() {
572       return "Union: (" + StringUtil.join(Arrays.asList(myScopes), ",") + ")";
573     }
574
575     @NotNull
576     @Override
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);
581       }
582       return super.uniteWith(scope);
583     }
584   }
585
586   @NotNull
587   public static GlobalSearchScope getScopeRestrictedByFileTypes(@NotNull GlobalSearchScope scope, @NotNull FileType... fileTypes) {
588     if (scope == EMPTY_SCOPE) {
589       return EMPTY_SCOPE;
590     }
591     LOG.assertTrue(fileTypes.length > 0);
592     return new FileTypeRestrictionScope(scope, fileTypes);
593   }
594
595   private static class FileTypeRestrictionScope extends DelegatingGlobalSearchScope {
596     private final FileType[] myFileTypes;
597
598     private FileTypeRestrictionScope(@NotNull GlobalSearchScope scope, @NotNull FileType[] fileTypes) {
599       super(scope);
600       myFileTypes = fileTypes;
601     }
602
603     @Override
604     public boolean contains(@NotNull VirtualFile file) {
605       if (!super.contains(file)) return false;
606
607       final FileType fileType = file.getFileType();
608       for (FileType otherFileType : myFileTypes) {
609         if (fileType.equals(otherFileType)) return true;
610       }
611
612       return false;
613     }
614
615     @NotNull
616     @Override
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()]));
624         }
625       }
626       return super.intersectWith(scope);
627     }
628
629     @NotNull
630     @Override
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));
636         }
637       }
638       return super.uniteWith(scope);
639     }
640
641     @Override
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;
646
647       FileTypeRestrictionScope that = (FileTypeRestrictionScope)o;
648
649       return Arrays.equals(myFileTypes, that.myFileTypes);
650     }
651
652     @Override
653     public int hashCode() {
654       int result = super.hashCode();
655       result = 31 * result + Arrays.hashCode(myFileTypes);
656       return result;
657     }
658
659     @Override
660     public String toString() {
661       return "(" + myBaseScope + " restricted by file types: "+Arrays.asList(myFileTypes)+")";
662     }
663   }
664
665   private static class EmptyScope extends GlobalSearchScope {
666     @Override
667     public boolean contains(@NotNull VirtualFile file) {
668       return false;
669     }
670
671     @Override
672     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
673       return 0;
674     }
675
676     @Override
677     public boolean isSearchInModuleContent(@NotNull Module aModule) {
678       return false;
679     }
680
681     @Override
682     public boolean isSearchInLibraries() {
683       return false;
684     }
685
686     @Override
687     @NotNull
688     public GlobalSearchScope intersectWith(@NotNull final GlobalSearchScope scope) {
689       return this;
690     }
691
692     @Override
693     @NotNull
694     public GlobalSearchScope uniteWith(@NotNull final GlobalSearchScope scope) {
695       return scope;
696     }
697
698     @Override
699     public String toString() {
700       return "EMPTY";
701     }
702   }
703
704   public static final GlobalSearchScope EMPTY_SCOPE = new EmptyScope();
705
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;
710
711     private FileScope(@NotNull Project project, @Nullable VirtualFile virtualFile) {
712       super(project);
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);
717     }
718
719     @Override
720     public boolean contains(@NotNull VirtualFile file) {
721       return Comparing.equal(myVirtualFile, file);
722     }
723
724     @Override
725     public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
726       return 0;
727     }
728
729     @Override
730     public boolean isSearchInModuleContent(@NotNull Module aModule) {
731       return aModule == myModule;
732     }
733
734     @Override
735     public boolean isSearchInLibraries() {
736       return myModule == null;
737     }
738
739     @Override
740     public String toString() {
741       return "File :"+myVirtualFile;
742     }
743
744     @Override
745     public Iterator<VirtualFile> iterator() {
746       return Collections.singletonList(myVirtualFile).iterator();
747     }
748
749     @Override
750     public boolean isSearchOutsideRootModel() {
751       return mySearchOutsideContent;
752     }
753   }
754
755   public static class FilesScope extends GlobalSearchScope implements Iterable<VirtualFile> {
756     private final Collection<VirtualFile> myFiles;
757     private Boolean myHasFilesOutOfProjectRoots;
758
759     /**
760      * @deprecated use {@link GlobalSearchScope#filesScope(Project, Collection)}
761      */
762     public FilesScope(@Nullable Project project, @NotNull Collection<VirtualFile> files) {
763       this(project, files, null);
764     }
765
766     // Optimization
767     private FilesScope(@Nullable  Project project, @NotNull Collection<VirtualFile> files, @Nullable Boolean hasFilesOutOfProjectRoots) {
768       super(project);
769       myFiles = files;
770       myHasFilesOutOfProjectRoots = hasFilesOutOfProjectRoots;
771     }
772
773     @Override
774     public boolean contains(@NotNull final VirtualFile file) {
775       return myFiles.contains(file);
776     }
777
778     @Override
779     public int compare(@NotNull final VirtualFile file1, @NotNull final VirtualFile file2) {
780       return 0;
781     }
782
783     @Override
784     public boolean isSearchInModuleContent(@NotNull Module aModule) {
785       return true;
786     }
787
788     @Override
789     public boolean isSearchInLibraries() {
790       return hasFilesOutOfProjectRoots();
791     }
792
793     @Override
794     public boolean equals(Object o) {
795       return this == o || o instanceof FilesScope && myFiles.equals(((FilesScope)o).myFiles);
796     }
797
798     @Override
799     public int hashCode() {
800       return myFiles.hashCode();
801     }
802     
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;
811               break;
812             }
813           }
814         }
815       }
816       return myHasFilesOutOfProjectRoots;
817     }
818
819     @Override
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");
823     }
824
825     @Override
826     public Iterator<VirtualFile> iterator() {
827       return myFiles.iterator();
828     }
829   }
830 }