move fileScope and filesScope back from GlobalSearchScopes to GlobalSearchScope
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / findUsages / GroovyConstructorUsagesSearcher.java
1 /*
2  * Copyright 2000-2009 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
17 package org.jetbrains.plugins.groovy.findUsages;
18
19 import com.intellij.openapi.application.QueryExecutorBase;
20 import com.intellij.openapi.application.ReadActionProcessor;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.roots.ProjectRootManager;
23 import com.intellij.openapi.util.Key;
24 import com.intellij.openapi.util.TextRange;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.psi.*;
27 import com.intellij.psi.impl.light.LightMemberReference;
28 import com.intellij.psi.search.*;
29 import com.intellij.psi.search.searches.AnnotatedElementsSearch;
30 import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
31 import com.intellij.psi.search.searches.MethodReferencesSearch;
32 import com.intellij.psi.search.searches.ReferencesSearch;
33 import com.intellij.psi.util.CachedValueProvider;
34 import com.intellij.psi.util.CachedValuesManager;
35 import com.intellij.psi.util.PsiModificationTracker;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import com.intellij.util.PairProcessor;
38 import com.intellij.util.Processor;
39 import com.intellij.util.containers.ConcurrentHashSet;
40 import com.intellij.util.containers.ContainerUtil;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.plugins.groovy.GroovyFileType;
44 import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
45 import org.jetbrains.plugins.groovy.gpp.GppTypeConverter;
46 import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
47 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
48 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
49 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation;
50 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
51 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
52 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
53 import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
54 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
55 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
56 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
57 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
58 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
59 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
60 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
61 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
62 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
63 import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction;
64 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
65
66 import java.util.Arrays;
67 import java.util.HashSet;
68 import java.util.Set;
69
70 /**
71  * @author Maxim.Medvedev
72  */
73 public class GroovyConstructorUsagesSearcher extends QueryExecutorBase<PsiReference, MethodReferencesSearch.SearchParameters> {
74   public GroovyConstructorUsagesSearcher() {
75     super(true);
76   }
77
78   @Override
79   public void processQuery(@NotNull MethodReferencesSearch.SearchParameters p, @NotNull Processor<PsiReference> consumer) {
80     processConstructorUsages(p.getMethod(), p.getScope(), consumer, p.getOptimizer(), true, !p.isStrictSignatureSearch());
81   }
82
83   public static final Key<Set<PsiClass>> LITERALLY_CONSTRUCTED_CLASSES = Key.create("LITERALLY_CONSTRUCTED_CLASSES");
84   static void processConstructorUsages(final PsiMethod constructor, final SearchScope searchScope, final Processor<PsiReference> consumer, final SearchRequestCollector collector, final boolean searchGppCalls, final boolean includeOverloads) {
85     if (!constructor.isConstructor()) return;
86
87     SearchScope onlyGroovy = PsiUtil.restrictScopeToGroovyFiles(searchScope);
88
89     final PsiClass clazz = constructor.getContainingClass();
90     if (clazz == null) return;
91
92     Set<PsiClass> processed = collector.getSearchSession().getUserData(LITERALLY_CONSTRUCTED_CLASSES);
93     if (processed == null) {
94       collector.getSearchSession().putUserData(LITERALLY_CONSTRUCTED_CLASSES, processed = new ConcurrentHashSet<PsiClass>());
95     }
96     if (!processed.add(clazz)) return;
97
98     if (clazz.isEnum() && clazz instanceof GroovyPsiElement) {
99       for (PsiField field : clazz.getFields()) {
100         if (field instanceof GrEnumConstant) {
101           final PsiReference ref = field.getReference();
102           if (ref != null && ref.isReferenceTo(constructor)) {
103             if (!consumer.process(ref)) return;
104           }
105         }
106       }
107     }
108
109     final LiteralConstructorSearcher literalProcessor = new LiteralConstructorSearcher(constructor, consumer, includeOverloads);
110
111     final Processor<GrNewExpression> newExpressionProcessor = new Processor<GrNewExpression>() {
112       @Override
113       public boolean process(GrNewExpression grNewExpression) {
114         final PsiMethod resolvedConstructor = grNewExpression.resolveMethod();
115         if (includeOverloads || constructor.getManager().areElementsEquivalent(resolvedConstructor, constructor)) {
116           return consumer.process(grNewExpression.getReferenceElement());
117         }
118         return true;
119       }
120     };
121
122     processGroovyClassUsages(clazz, searchScope, collector, searchGppCalls, newExpressionProcessor, literalProcessor);
123
124     //this()
125     if (clazz instanceof GrTypeDefinition) {
126       if (!processConstructors(constructor, consumer, clazz, true)) {
127         return;
128       }
129     }
130     //super()
131     DirectClassInheritorsSearch.search(clazz, onlyGroovy).forEach(new ReadActionProcessor<PsiClass>() {
132       @Override
133       public boolean processInReadAction(PsiClass inheritor) {
134         if (inheritor instanceof GrTypeDefinition) {
135           if (!processConstructors(constructor, consumer, inheritor, false)) return false;
136         }
137         return true;
138       }
139     });
140   }
141
142   public static void processGroovyClassUsages(final PsiClass clazz,
143                                               final SearchScope scope,
144                                               SearchRequestCollector collector,
145                                               final boolean searchGppCalls,
146                                               final Processor<GrNewExpression> newExpressionProcessor,
147                                               final LiteralConstructorSearcher literalProcessor) {
148     final Set<PsiAnchor> processedMethods = new ConcurrentHashSet<PsiAnchor>();
149
150     ReferencesSearch.searchOptimized(clazz, scope, true, collector, true, new PairProcessor<PsiReference, SearchRequestCollector>() {
151       @Override
152       public boolean process(PsiReference ref, SearchRequestCollector collector) {
153         final PsiElement element = ref.getElement();
154
155         if (element instanceof GrCodeReferenceElement) {
156           if (!processGroovyConstructorUsages((GrCodeReferenceElement)element, !searchGppCalls, newExpressionProcessor, literalProcessor)) {
157             return false;
158           }
159         }
160
161         if (searchGppCalls) {
162           final PsiMethod method = getMethodToSearchForCallsWithLiteralArguments(element, clazz);
163           if (method != null && processedMethods.add(PsiAnchor.create(method))) {
164             processGppMethodCalls(clazz, scope, collector, method, literalProcessor);
165           }
166         }
167         return true;
168       }
169     });
170   }
171
172   @Nullable
173   private static PsiMethod getMethodToSearchForCallsWithLiteralArguments(PsiElement element, PsiClass targetClass) {
174     final PsiParameter parameter = PsiTreeUtil.getParentOfType(element, PsiParameter.class);
175     if (parameter != null) {
176       final PsiMethod method = PsiTreeUtil.getParentOfType(parameter, PsiMethod.class);
177       if (method != null) {
178         final PsiParameter[] parameters = method.getParameterList().getParameters();
179         final int idx = Arrays.asList(parameters).indexOf(parameter);
180         if (idx >= 0) {
181           PsiType parameterType = parameter.getType();
182           if (parameterType instanceof PsiArrayType && idx == parameters.length - 1) {
183             parameterType = ((PsiArrayType)parameterType).getComponentType();
184           }
185           if (parameterType instanceof PsiClassType) {
186             if (method.getManager().areElementsEquivalent(targetClass, ((PsiClassType)parameterType).resolve())) {
187               return method;
188             }
189           }
190         }
191       }
192     }
193     return null;
194   }
195
196   private static void processGppMethodCalls(final PsiClass targetClass,
197                                             SearchScope scope,
198                                             SearchRequestCollector originalCollector, @NotNull PsiMethod currentTarget,
199                                             final LiteralConstructorSearcher literalProcessor) {
200     final SearchScope gppScope = getGppScope(targetClass.getProject()).intersectWith(scope);
201
202     if (gppScope instanceof GlobalSearchScope) {
203       String name = currentTarget.getName();
204       if (PsiSearchHelper.SERVICE.getInstance(currentTarget.getProject()).isCheapEnoughToSearch(name, (GlobalSearchScope)gppScope, null,
205                                                                                                 null) ==
206           PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
207         return;
208       }
209     }
210
211     final ReadActionProcessor<PsiReference> gppCallProcessor = new ReadActionProcessor<PsiReference>() {
212
213       @Nullable
214       private GrExpression[] getCallArguments(PsiReference psiReference) {
215         if (psiReference instanceof GrReferenceElement) {
216           final PsiElement parent = ((GrReferenceElement)psiReference).getParent();
217           if (parent instanceof GrCall) {
218             final GrArgumentList argList = ((GrCall)parent).getArgumentList();
219             if (argList != null) {
220               return argList.getExpressionArguments();
221             }
222           }
223         }
224         else if (psiReference instanceof LiteralConstructorReference) {
225           return ((LiteralConstructorReference)psiReference).getCallArguments();
226         }
227         return null;
228       }
229
230       @Override
231       public boolean processInReadAction(PsiReference psiReference) {
232         final GrExpression[] arguments = getCallArguments(psiReference);
233         if (arguments == null) {
234           return true;
235         }
236
237         boolean checkedTypedContext = false;
238         for (GrExpression argument : arguments) {
239           if (argument instanceof GrListOrMap) {
240             if (!checkedTypedContext) {
241               if (!GppTypeConverter.hasTypedContext(psiReference.getElement())) {
242                 return true;
243               }
244               checkedTypedContext = true;
245             }
246
247             if (!literalProcessor.processLiteral((GrListOrMap)argument, true)) {
248               return false;
249             }
250           }
251         }
252         return true;
253       }
254     };
255     if (currentTarget.isConstructor()) {
256       processConstructorUsages(currentTarget, gppScope, gppCallProcessor, originalCollector, true, false);
257     }
258     else {
259       MethodReferencesSearch.searchOptimized(currentTarget, gppScope, true, originalCollector, gppCallProcessor);
260     }
261   }
262
263   private static GlobalSearchScope getGppScope(final Project project) {
264     return CachedValuesManager.getManager(project).getCachedValue(project, new CachedValueProvider<GlobalSearchScope>() {
265       @Override
266       public Result<GlobalSearchScope> compute() {
267         return Result.create(calcGppScope(project), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT, ProjectRootManager.getInstance(project));
268       }
269     });
270   }
271
272   private static boolean processGroovyConstructorUsages(GrCodeReferenceElement element,
273                                                         boolean usualCallsOnly,
274                                                         final Processor<GrNewExpression> newExpressionProcessor,
275                                                         final LiteralConstructorSearcher literalProcessor) {
276     PsiElement parent = element.getParent();
277
278     if (parent instanceof GrAnonymousClassDefinition) {
279       parent = parent.getParent();
280     }
281     if (parent instanceof GrNewExpression) {
282       return newExpressionProcessor.process((GrNewExpression)parent);
283     }
284
285     if (usualCallsOnly) {
286       return true;
287     }
288
289     if (parent instanceof GrTypeElement) {
290       final GrTypeElement typeElement = (GrTypeElement)parent;
291
292       final PsiElement grandpa = typeElement.getParent();
293       if (grandpa instanceof GrVariableDeclaration) {
294         final GrVariable[] vars = ((GrVariableDeclaration)grandpa).getVariables();
295         if (vars.length == 1) {
296           final GrVariable variable = vars[0];
297           if (!checkLiteralInstantiation(variable.getInitializerGroovy(), literalProcessor)) {
298             return false;
299           }
300         }
301       }
302       else if (grandpa instanceof GrMethod) {
303         final GrMethod method = (GrMethod)grandpa;
304         if (typeElement == method.getReturnTypeElementGroovy()) {
305           ControlFlowUtils.visitAllExitPoints(method.getBlock(), new ControlFlowUtils.ExitPointVisitor() {
306             @Override
307             public boolean visitExitPoint(Instruction instruction, @Nullable GrExpression returnValue) {
308               if (!checkLiteralInstantiation(returnValue, literalProcessor)) {
309                 return false;
310               }
311               return true;
312             }
313           });
314         }
315       }
316       else if (grandpa instanceof GrTypeCastExpression) {
317         final GrTypeCastExpression cast = (GrTypeCastExpression)grandpa;
318         if (cast.getCastTypeElement() == typeElement &&
319             !checkLiteralInstantiation(cast.getOperand(), literalProcessor)) {
320           return false;
321         }
322       }
323       else if (grandpa instanceof GrSafeCastExpression) {
324         final GrSafeCastExpression cast = (GrSafeCastExpression)grandpa;
325         if (cast.getCastTypeElement() == typeElement &&
326             !checkLiteralInstantiation(cast.getOperand(), literalProcessor)) {
327           return false;
328         }
329       }
330     }
331     return true;
332   }
333
334   private static GlobalSearchScope calcGppScope(Project project) {
335     final GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
336     final GlobalSearchScope maximal = GlobalSearchScope.getScopeRestrictedByFileTypes(allScope, GroovyFileType.GROOVY_FILE_TYPE);
337     GlobalSearchScope gppExtensions = new DelegatingGlobalSearchScope(maximal, "groovy.gpp") {
338       @Override
339       public boolean contains(VirtualFile file) {
340         return super.contains(file) && GppTypeConverter.isGppExtension(file.getExtension());
341       }
342     };
343     final PsiClass typed = JavaPsiFacade.getInstance(project).findClass(GppTypeConverter.GROOVY_LANG_TYPED, allScope);
344     if (typed != null) {
345       final Set<VirtualFile> files = new HashSet<VirtualFile>();
346       AnnotatedElementsSearch.searchElements(typed, maximal, PsiModifierListOwner.class).forEach(new Processor<PsiModifierListOwner>() {
347         @Override
348         public boolean process(PsiModifierListOwner occurrence) {
349           ContainerUtil.addIfNotNull(occurrence.getContainingFile().getVirtualFile(), files);
350           return true;
351         }
352       });
353
354       GlobalSearchScope withTypedAnno = GlobalSearchScope.filesScope(project, files);
355       return withTypedAnno.union(gppExtensions);
356     }
357
358     return gppExtensions;
359   }
360
361   private static boolean checkLiteralInstantiation(GrExpression expression,
362                                                    final LiteralConstructorSearcher literalProcessor) {
363
364     if (expression instanceof GrListOrMap) {
365       return literalProcessor.processLiteral((GrListOrMap)expression, GppTypeConverter.hasTypedContext(expression));
366     }
367     return true;
368   }
369
370   private static boolean processConstructors(final PsiMethod searchedConstructor, final Processor<PsiReference> consumer, final PsiClass clazz,
371                                              final boolean processThisRefs) {
372     final PsiMethod[] constructors = clazz.getConstructors();
373     if (constructors.length == 0) {
374       processImplicitConstructorCall(clazz, consumer, searchedConstructor);
375     }
376     for (PsiMethod constructor : constructors) {
377       final GrOpenBlock block = ((GrMethod)constructor).getBlock();
378       if (block != null) {
379         final GrStatement[] statements = block.getStatements();
380         if (statements.length > 0 && statements[0] instanceof GrConstructorInvocation) {
381           final GrConstructorInvocation invocation = (GrConstructorInvocation)statements[0];
382           if (invocation.isThisCall() == processThisRefs &&
383               invocation.getManager().areElementsEquivalent(invocation.resolveMethod(), searchedConstructor) &&
384               !consumer.process(invocation.getThisOrSuperKeyword())) {
385             return false;
386           }
387         }
388         else {
389           processImplicitConstructorCall(constructor, consumer, searchedConstructor);
390         }
391       }
392     }
393     return true;
394   }
395
396   private static void processImplicitConstructorCall(final PsiMember usage,
397                                                      final Processor<PsiReference> processor,
398                                                      final PsiMethod constructor) {
399     if (constructor instanceof GrMethod) {
400       GrParameter[] grParameters = (GrParameter[])constructor.getParameterList().getParameters();
401       if (grParameters.length > 0 && !grParameters[0].isOptional()) return;
402     }
403     else if (constructor.getParameterList().getParameters().length > 0) return;
404
405
406     PsiManager manager = constructor.getManager();
407     if (manager.areElementsEquivalent(usage, constructor) || manager.areElementsEquivalent(constructor.getContainingClass(), usage.getContainingClass())) return;
408     processor.process(new LightMemberReference(manager, usage, PsiSubstitutor.EMPTY) {
409       public PsiElement getElement() {
410         return usage;
411       }
412
413       public TextRange getRangeInElement() {
414         if (usage instanceof PsiClass) {
415           PsiIdentifier identifier = ((PsiClass)usage).getNameIdentifier();
416           if (identifier != null) return TextRange.from(identifier.getStartOffsetInParent(), identifier.getTextLength());
417         }
418         else if (usage instanceof PsiMethod) {
419           PsiIdentifier identifier = ((PsiMethod)usage).getNameIdentifier();
420           if (identifier != null) return TextRange.from(identifier.getStartOffsetInParent(), identifier.getTextLength());
421         }
422         return super.getRangeInElement();
423       }
424     });
425
426   }
427
428 }