move fileScope and filesScope back from GlobalSearchScopes to GlobalSearchScope
[idea/community.git] / platform / core-api / src / com / intellij / psi / search / GlobalSearchScope.java
1 /*
2  * Copyright 2000-2011 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.components.ServiceManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.fileTypes.FileType;
21 import com.intellij.openapi.fileTypes.FileTypeRegistry;
22 import com.intellij.openapi.module.Module;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.roots.FileIndexFacade;
25 import com.intellij.openapi.util.Comparing;
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 org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Collection;
37 import java.util.List;
38
39 public abstract class GlobalSearchScope extends SearchScope implements ProjectAwareFileFilter {
40   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.search.GlobalSearchScope");
41   @Nullable private final Project myProject;
42
43   protected GlobalSearchScope(Project project) {
44     myProject = project;
45   }
46
47   protected GlobalSearchScope() {
48     this(null);
49   }
50
51   public abstract boolean contains(VirtualFile file);
52
53   public Project getProject() {
54     return myProject;
55   }
56
57   /**
58    * @param file1
59    * @param file2
60    * @return a positive integer (+1), if file1 is located in the classpath before file2,
61    *         a negative integer (-1), if file1 is located in the classpath after file2
62    *         zero - otherwise or when the file are not comparable.
63    */
64   public abstract int compare(VirtualFile file1, VirtualFile file2);
65
66   // optimization methods:
67
68   public abstract boolean isSearchInModuleContent(@NotNull Module aModule);
69
70   public boolean isSearchInModuleContent(@NotNull Module aModule, boolean testSources) {
71     return isSearchInModuleContent(aModule);
72   }
73
74   public boolean accept(VirtualFile file) {
75     return contains(file);
76   }
77
78   public abstract boolean isSearchInLibraries();
79
80   public boolean isSearchOutsideRootModel() {
81     return false;
82   }
83
84   @NotNull
85   public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
86     if (scope == this) return this;
87     if (scope instanceof IntersectionScope) {
88       return scope.intersectWith(this);
89     }
90     return new IntersectionScope(this, scope, null);
91   }
92
93   @NotNull
94   @Override
95   public SearchScope intersectWith(@NotNull SearchScope scope2) {
96     if (scope2 instanceof LocalSearchScope) {
97       LocalSearchScope localScope2 = (LocalSearchScope)scope2;
98       return intersectWith(localScope2);
99     }
100     return intersectWith((GlobalSearchScope)scope2);
101   }
102
103   public SearchScope intersectWith(LocalSearchScope localScope2) {
104     PsiElement[] elements2 = localScope2.getScope();
105     List<PsiElement> result = new ArrayList<PsiElement>(elements2.length);
106     for (final PsiElement element2 : elements2) {
107       if (PsiSearchScopeUtil.isInScope(this, element2)) {
108         result.add(element2);
109       }
110     }
111     return new LocalSearchScope(result.toArray(new PsiElement[result.size()]), null, localScope2.isIgnoreInjectedPsi());
112   }
113
114   @NotNull
115   public GlobalSearchScope union(@NotNull SearchScope scope) {
116     if (scope instanceof GlobalSearchScope) return uniteWith((GlobalSearchScope)scope);
117     return union((LocalSearchScope)scope);
118   }
119
120   @NotNull
121   public GlobalSearchScope union(final LocalSearchScope scope) {
122     return new GlobalSearchScope(scope.getScope()[0].getProject()) {
123       @Override
124       public boolean contains(VirtualFile file) {
125         return GlobalSearchScope.this.contains(file) || scope.isInScope(file);
126       }
127
128       @Override
129       public int compare(VirtualFile file1, VirtualFile file2) {
130         return GlobalSearchScope.this.contains(file1) && GlobalSearchScope.this.contains(file2) ? GlobalSearchScope.this.compare(file1, file2) : 0;
131       }
132
133       @Override
134       public boolean isSearchInModuleContent(@NotNull Module aModule) {
135         return GlobalSearchScope.this.isSearchInModuleContent(aModule);
136       }
137
138       @Override
139       public boolean isSearchOutsideRootModel() {
140         return GlobalSearchScope.this.isSearchOutsideRootModel();
141       }
142
143       @Override
144       public boolean isSearchInLibraries() {
145         return GlobalSearchScope.this.isSearchInLibraries();
146       }
147     };
148   }
149
150   public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
151     if (scope == this) return scope;
152     return new UnionScope(this, scope, null);
153   }
154
155   public static GlobalSearchScope allScope(@NotNull Project project) {
156     return ProjectScope.getAllScope(project);
157   }
158
159   public static GlobalSearchScope projectScope(@NotNull Project project) {
160     return ProjectScope.getProjectScope(project);
161   }
162
163   public static GlobalSearchScope notScope(@NotNull final GlobalSearchScope scope) {
164     return new DelegatingGlobalSearchScope(scope) {
165       public boolean contains(final VirtualFile file) {
166         return !myBaseScope.contains(file);
167       }
168
169       @Override
170       public boolean isSearchOutsideRootModel() {
171         return true;
172       }
173     };
174   }
175
176   /**
177    * Returns module scope including sources and tests, excluding libraries and dependencies.
178    *
179    * @param module the module to get the scope.
180    * @return scope including sources and tests, excluding libraries and dependencies.
181    */
182   public static GlobalSearchScope moduleScope(@NotNull Module module) {
183     return module.getModuleScope();
184   }
185
186   /**
187    * Returns module scope including sources, tests, and libraries, excluding dependencies.
188    *
189    * @param module the module to get the scope.
190    * @return scope including sources, tests, and libraries, excluding dependencies.
191    */
192   public static GlobalSearchScope moduleWithLibrariesScope(@NotNull Module module) {
193     return module.getModuleWithLibrariesScope();
194   }
195
196   /**
197    * Returns module scope including sources, tests, and dependencies, excluding libraries.
198    *
199    * @param module the module to get the scope.
200    * @return scope including sources, tests, and dependencies, excluding libraries.
201    */
202   public static GlobalSearchScope moduleWithDependenciesScope(@NotNull Module module) {
203     return module.getModuleWithDependenciesScope();
204   }
205
206   public static GlobalSearchScope moduleRuntimeScope(@NotNull Module module, final boolean includeTests) {
207     return module.getModuleRuntimeScope(includeTests);
208   }
209
210   public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module) {
211     return moduleWithDependenciesAndLibrariesScope(module, true);
212   }
213
214   public static GlobalSearchScope moduleWithDependenciesAndLibrariesScope(@NotNull Module module, boolean includeTests) {
215     return module.getModuleWithDependenciesAndLibrariesScope(includeTests);
216   }
217
218   public static GlobalSearchScope moduleWithDependentsScope(@NotNull Module module) {
219     return module.getModuleWithDependentsScope();
220   }
221
222   public static GlobalSearchScope moduleTestsWithDependentsScope(@NotNull Module module) {
223     return module.getModuleWithDependentsScope();
224   }
225
226   public static GlobalSearchScope fileScope(@NotNull PsiFile psiFile) {
227     return new FileScope(psiFile.getProject(), psiFile.getVirtualFile());
228   }
229
230   public static GlobalSearchScope fileScope(final Project project, final VirtualFile virtualFile) {
231     return new FileScope(project, virtualFile);
232   }
233
234   public static GlobalSearchScope filesScope(final Project project, final Collection<VirtualFile> files) {
235     if (files.isEmpty()) return EMPTY_SCOPE;
236     return files.size() == 1? fileScope(project, files.iterator().next()) : new FilesScope(project, files);
237   }
238
239   static class IntersectionScope extends GlobalSearchScope {
240     private final GlobalSearchScope myScope1;
241     private final GlobalSearchScope myScope2;
242     private final String myDisplayName;
243
244     IntersectionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2, String displayName) {
245       super(scope1.getProject() == null ? scope2.getProject() : scope1.getProject());
246       myScope1 = scope1;
247       myScope2 = scope2;
248       myDisplayName = displayName;
249     }
250
251     @NotNull
252     @Override
253     public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
254       if (myScope1.equals(scope) || myScope2.equals(scope)) {
255         return this;
256       }
257       return new IntersectionScope(this, scope, null);
258     }
259
260     public String getDisplayName() {
261       if (myDisplayName == null) {
262         return PsiBundle.message("psi.search.scope.intersection", myScope1.getDisplayName(), myScope2.getDisplayName());
263       }
264       return myDisplayName;
265     }
266
267     public boolean contains(VirtualFile file) {
268       return myScope1.contains(file) && myScope2.contains(file);
269     }
270
271     public int compare(VirtualFile file1, VirtualFile file2) {
272       int res1 = myScope1.compare(file1, file2);
273       int res2 = myScope2.compare(file1, file2);
274
275       if (res1 == 0) return res2;
276       if (res2 == 0) return res1;
277
278       res1 /= Math.abs(res1);
279       res2 /= Math.abs(res2);
280       if (res1 == res2) return res1;
281
282       return 0;
283     }
284
285     public boolean isSearchInModuleContent(@NotNull Module aModule) {
286       return myScope1.isSearchInModuleContent(aModule) && myScope2.isSearchInModuleContent(aModule);
287     }
288
289     public boolean isSearchInModuleContent(@NotNull final Module aModule, final boolean testSources) {
290       return myScope1.isSearchInModuleContent(aModule, testSources) && myScope2.isSearchInModuleContent(aModule, testSources);
291     }
292
293     public boolean isSearchInLibraries() {
294       return myScope1.isSearchInLibraries() && myScope2.isSearchInLibraries();
295     }
296     
297     public boolean isSearchOutsideRootModel() {
298       return myScope1.isSearchOutsideRootModel() && myScope2.isSearchOutsideRootModel();
299     }
300
301     @Override
302     public boolean equals(Object o) {
303       if (this == o) return true;
304       if (!(o instanceof IntersectionScope)) return false;
305
306       IntersectionScope that = (IntersectionScope)o;
307
308       if (!myScope1.equals(that.myScope1)) return false;
309       if (!myScope2.equals(that.myScope2)) return false;
310
311       return true;
312     }
313
314     @Override
315     public int hashCode() {
316       return 31 * myScope1.hashCode() + myScope2.hashCode();
317     }
318   }
319   private static class UnionScope extends GlobalSearchScope {
320     private final GlobalSearchScope myScope1;
321     private final GlobalSearchScope myScope2;
322     private final String myDisplayName;
323
324     private UnionScope(@NotNull GlobalSearchScope scope1, @NotNull GlobalSearchScope scope2, String displayName) {
325       super(scope1.getProject() == null ? scope2.getProject() : scope1.getProject());
326       myScope1 = scope1;
327       myScope2 = scope2;
328       myDisplayName = displayName;
329     }
330
331     public String getDisplayName() {
332       if (myDisplayName == null) {
333         return PsiBundle.message("psi.search.scope.union", myScope1.getDisplayName(), myScope2.getDisplayName());
334       }
335       return myDisplayName;
336     }
337
338     public boolean contains(VirtualFile file) {
339       return myScope1.contains(file) || myScope2.contains(file);
340     }
341
342     @Override
343     public boolean isSearchOutsideRootModel() {
344       return myScope1.isSearchOutsideRootModel() || myScope2.isSearchOutsideRootModel();
345     }
346
347     public int compare(VirtualFile file1, VirtualFile file2) {
348       int res1 = myScope1.contains(file1) && myScope1.contains(file2) ? myScope1.compare(file1, file2) : 0;
349       int res2 = myScope2.contains(file1) && myScope2.contains(file2) ? myScope2.compare(file1, file2) : 0;
350
351       if (res1 == 0) return res2;
352       if (res2 == 0) return res1;
353
354       res1 /= Math.abs(res1);
355       res2 /= Math.abs(res2);
356       if (res1 == res2) return res1;
357
358       return 0;
359     }
360
361     public boolean isSearchInModuleContent(@NotNull Module aModule) {
362       return myScope1.isSearchInModuleContent(aModule) || myScope2.isSearchInModuleContent(aModule);
363     }
364
365     public boolean isSearchInModuleContent(@NotNull final Module aModule, final boolean testSources) {
366       return myScope1.isSearchInModuleContent(aModule, testSources) || myScope2.isSearchInModuleContent(aModule, testSources);
367     }
368
369     public boolean isSearchInLibraries() {
370       return myScope1.isSearchInLibraries() || myScope2.isSearchInLibraries();
371     }
372
373     @Override
374     public boolean equals(Object o) {
375       if (this == o) return true;
376       if (!(o instanceof UnionScope)) return false;
377
378       UnionScope that = (UnionScope)o;
379
380       if (!myScope1.equals(that.myScope1)) return false;
381       if (!myScope2.equals(that.myScope2)) return false;
382
383       return true;
384     }
385
386     @Override
387     public int hashCode() {
388       return 31 * myScope1.hashCode() + myScope2.hashCode();
389     }
390   }
391
392   @NotNull
393   public static GlobalSearchScope getScopeRestrictedByFileTypes (@NotNull GlobalSearchScope scope, final FileType... fileTypes) {
394     LOG.assertTrue(fileTypes.length > 0);
395     return new FileTypeRestrictionScope(scope, fileTypes);
396   }
397
398   private static class FileTypeRestrictionScope extends DelegatingGlobalSearchScope {
399     private final FileType[] myFileTypes;
400
401     private FileTypeRestrictionScope(@NotNull GlobalSearchScope scope, @NotNull FileType[] fileTypes) {
402       super(scope);
403       myFileTypes = fileTypes;
404     }
405
406     public boolean contains(VirtualFile file) {
407       if (!super.contains(file)) return false;
408
409       final FileType fileType = FileTypeRegistry.getInstance().getFileTypeByFile(file);
410       for (FileType otherFileType : myFileTypes) {
411         if (fileType.equals(otherFileType)) return true;
412       }
413
414       return false;
415     }
416
417     @NotNull
418     @Override
419     public GlobalSearchScope intersectWith(@NotNull GlobalSearchScope scope) {
420       if (scope instanceof FileTypeRestrictionScope) {
421         FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope;
422         if (restrict.myBaseScope == myBaseScope) {
423           List<FileType> intersection = new ArrayList<FileType>(Arrays.asList(restrict.myFileTypes));
424           intersection.retainAll(Arrays.asList(myFileTypes));
425           return new FileTypeRestrictionScope(myBaseScope, intersection.toArray(new FileType[intersection.size()]));
426         }
427       }
428       return super.intersectWith(scope);
429     }
430
431     @Override
432     public GlobalSearchScope uniteWith(@NotNull GlobalSearchScope scope) {
433       if (scope instanceof FileTypeRestrictionScope) {
434         FileTypeRestrictionScope restrict = (FileTypeRestrictionScope)scope;
435         if (restrict.myBaseScope == myBaseScope) {
436           return new FileTypeRestrictionScope(myBaseScope, ArrayUtil.mergeArrays(myFileTypes, restrict.myFileTypes));
437         }
438       }
439       return super.uniteWith(scope);
440     }
441
442     @Override
443     public boolean equals(Object o) {
444       if (this == o) return true;
445       if (!(o instanceof FileTypeRestrictionScope)) return false;
446
447       FileTypeRestrictionScope that = (FileTypeRestrictionScope)o;
448
449       if (!Arrays.equals(myFileTypes, that.myFileTypes)) return false;
450
451       return true;
452     }
453
454     @Override
455     public int hashCode() {
456       int result = super.hashCode();
457       result = 31 * result + Arrays.hashCode(myFileTypes);
458       return result;
459     }
460   }
461
462   private static class EmptyScope extends GlobalSearchScope {
463     public boolean contains(VirtualFile file) {
464       return false;
465     }
466
467     public int compare(VirtualFile file1, VirtualFile file2) {
468       return 0;
469     }
470
471     public boolean isSearchInModuleContent(@NotNull Module aModule) {
472       return false;
473     }
474
475     public boolean isSearchInLibraries() {
476       return false;
477     }
478
479     @NotNull
480     public GlobalSearchScope intersectWith(@NotNull final GlobalSearchScope scope) {
481       return this;
482     }
483
484     public GlobalSearchScope uniteWith(@NotNull final GlobalSearchScope scope) {
485       return scope;
486     }
487   }
488
489   public static final GlobalSearchScope EMPTY_SCOPE = new EmptyScope();
490
491   private static class FileScope extends GlobalSearchScope {
492     private final VirtualFile myVirtualFile;
493     private final Module myModule;
494
495     private FileScope(final Project project, final VirtualFile virtualFile) {
496       super(project);
497       myVirtualFile = virtualFile;
498       FileIndexFacade fileIndex = ServiceManager.getService(project, FileIndexFacade.class);
499       myModule = myVirtualFile != null ? fileIndex.getModuleForFile(myVirtualFile) : null;
500     }
501
502     public boolean contains(VirtualFile file) {
503       return Comparing.equal(myVirtualFile, file);
504     }
505
506     public int compare(VirtualFile file1, VirtualFile file2) {
507       return 0;
508     }
509
510     public boolean isSearchInModuleContent(@NotNull Module aModule) {
511       return aModule == myModule;
512     }
513
514     public boolean isSearchInLibraries() {
515       return myModule == null;
516     }
517   }
518
519   private static class FilesScope extends GlobalSearchScope {
520     private final Collection<VirtualFile> myFiles;
521
522     public FilesScope(final Project project, final Collection<VirtualFile> files) {
523       super(project);
524       myFiles = files;
525     }
526
527     @Override
528     public boolean contains(final VirtualFile file) {
529       return myFiles.contains(file);
530     }
531
532     @Override
533     public int compare(final VirtualFile file1, final VirtualFile file2) {
534       return 0;
535     }
536
537     @Override
538     public boolean isSearchInModuleContent(@NotNull Module aModule) {
539       return true;
540     }
541
542     @Override
543     public boolean isSearchInLibraries() {
544       return false;
545     }
546
547     @Override
548     public boolean equals(Object o) {
549       if (this == o) return true;
550       if (!(o instanceof FilesScope)) return false;
551
552       return myFiles.equals(((FilesScope)o).myFiles);
553     }
554
555     @Override
556     public int hashCode() {
557       return myFiles.hashCode();
558     }
559   }
560 }