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