2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions;
18 import com.intellij.codeInsight.PsiEquivalenceUtil;
19 import com.intellij.codeInsight.lookup.LookupElement;
20 import com.intellij.codeInsight.lookup.LookupElementBuilder;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Condition;
23 import com.intellij.psi.*;
24 import com.intellij.psi.search.GlobalSearchScope;
25 import com.intellij.psi.util.InheritanceUtil;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.TypeConversionUtil;
28 import com.intellij.util.ArrayUtil;
29 import com.intellij.util.Consumer;
30 import com.intellij.util.containers.ContainerUtil;
31 import gnu.trove.THashSet;
32 import org.jetbrains.plugins.groovy.GroovyIcons;
33 import org.jetbrains.plugins.groovy.lang.completion.GroovyCompletionUtil;
34 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
35 import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement;
36 import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
37 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
38 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrCall;
41 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrConstructorCall;
42 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
43 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
44 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrCallExpression;
45 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
46 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod;
47 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember;
48 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
49 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager;
50 import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
51 import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils;
52 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
53 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
54 import org.jetbrains.plugins.groovy.lang.resolve.processors.CompletionProcessor;
55 import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;
57 import java.util.ArrayList;
58 import java.util.LinkedHashSet;
59 import java.util.List;
65 public class CompleteReferenceExpression {
66 private CompleteReferenceExpression() {
69 public static void processVariants(Consumer<Object> consumer, GrReferenceExpressionImpl refExpr) {
70 PsiElement refParent = refExpr.getParent();
71 processArgsVariants(consumer, refParent);
73 final GrExpression qualifier = refExpr.getQualifierExpression();
74 Object[] propertyVariants = getVariantsImpl(refExpr, CompletionProcessor.createPropertyCompletionProcessor(refExpr));
76 if (qualifier == null) {
77 if (refParent instanceof GrArgumentList) {
78 final PsiElement argList = refParent.getParent();
79 if (argList instanceof GrExpression) {
80 GrExpression call = (GrExpression)argList; //add named argument label variants
81 type = call.getType();
86 type = qualifier.getType();
89 if (type instanceof PsiClassType) {
90 PsiClass clazz = ((PsiClassType)type).resolve();
92 Set<String> accessedPropertyNames = processPropertyVariants(consumer, refExpr, clazz);
93 for (Object variant : propertyVariants) {
94 if (variant instanceof PsiField && accessedPropertyNames.contains(((PsiField)variant).getName())) {
97 consumer.consume(variant);
102 for (Object variant : propertyVariants) {
103 consumer.consume(variant);
107 if (refExpr.getKind() == GrReferenceExpressionImpl.Kind.TYPE_OR_PROPERTY) {
108 ResolverProcessor classVariantsCollector = CompletionProcessor.createClassCompletionProcessor(refExpr);
109 final Object[] classVariants = getVariantsImpl(refExpr, classVariantsCollector);
110 for (Object variant : classVariants) {
111 consumer.consume(variant);
116 private static void processArgsVariants(Consumer<Object> consumer, PsiElement refParent) {
117 if (refParent instanceof GrArgumentList) {
118 PsiElement refPParent = refParent.getParent();
119 if (refPParent instanceof GrCall) {
120 GroovyResolveResult[] results = new GroovyResolveResult[0];
122 if (refPParent instanceof GrConstructorCall) {
123 GrConstructorCall constructorCall = (GrConstructorCall)refPParent;
124 results = ArrayUtil.mergeArrays(results, constructorCall.multiResolveConstructor(), GroovyResolveResult.class);
127 //call expression (method or new expression)
128 if (refPParent instanceof GrCallExpression) {
129 GrCallExpression constructorCall = (GrCallExpression)refPParent;
130 results = ArrayUtil.mergeArrays(results, constructorCall.getMethodVariants(), GroovyResolveResult.class);
132 else if (refPParent instanceof GrApplicationStatementImpl) {
133 final GrExpression element = ((GrApplicationStatementImpl)refPParent).getFunExpression();
134 if (element instanceof GrReferenceElement) {
135 results = ArrayUtil.mergeArrays(results, ((GrReferenceElement)element).multiResolve(true), GroovyResolveResult.class);
140 for (GroovyResolveResult result : results) {
141 PsiElement element = result.getElement();
142 if (element instanceof GrMethod) {
143 Set<String>[] parametersArray = ((GrMethod)element).getNamedParametersArray();
144 for (Set<String> namedParameters : parametersArray) {
145 for (String parameter : namedParameters) {
146 consumer.consume(parameter);
155 private static Set<String> processPropertyVariants(Consumer<Object> consumer, GrReferenceExpression refExpr, PsiClass clazz) {
156 Set<String> accessedPropertyNames=new THashSet<String>();
157 final PsiClass eventListener =
158 JavaPsiFacade.getInstance(refExpr.getProject()).findClass("java.util.EventListener", refExpr.getResolveScope());
159 for (PsiMethod method : clazz.getAllMethods()) {
160 if (PsiUtil.isStaticsOK(method, refExpr)) {
161 if (GroovyPropertyUtils.isSimplePropertyAccessor(method)) {
162 String prop = GroovyPropertyUtils.getPropertyName(method);
163 accessedPropertyNames.add(prop);
165 consumer.consume(LookupElementBuilder.create(prop).setIcon(GroovyIcons.PROPERTY));
167 else if (eventListener != null) {
168 consumeListenerProperties(consumer, method, eventListener);
172 return accessedPropertyNames;
175 private static void consumeListenerProperties(Consumer<Object> consumer, PsiMethod method, PsiClass eventListenerClass) {
176 if (method.getName().startsWith("add") && method.getParameterList().getParametersCount() == 1) {
177 final PsiParameter parameter = method.getParameterList().getParameters()[0];
178 final PsiType type = parameter.getType();
179 if (type instanceof PsiClassType) {
180 final PsiClassType classType = (PsiClassType)type;
181 final PsiClass listenerClass = classType.resolve();
182 if (listenerClass != null) {
183 final PsiMethod[] listenerMethods = listenerClass.getMethods();
184 if (InheritanceUtil.isInheritorOrSelf(listenerClass, eventListenerClass, true)) {
185 for (PsiMethod listenerMethod : listenerMethods) {
186 consumer.consume(LookupElementBuilder.create(listenerMethod.getName()).setIcon(GroovyIcons.PROPERTY));
195 private static Object[] getVariantsImpl(GrReferenceExpression refExpr, ResolverProcessor processor) {
196 GrExpression qualifier = refExpr.getQualifierExpression();
197 String[] sameQualifier = getVariantsWithSameQualifier(qualifier, refExpr);
198 if (qualifier == null) {
199 ResolveUtil.treeWalkUp(refExpr, processor, true);
200 qualifier = PsiImplUtil.getRuntimeQualifier(refExpr);
201 if (qualifier != null) {
202 getVariantsFromQualifier(refExpr, processor, qualifier);
206 if (refExpr.getDotTokenType() != GroovyTokenTypes.mSPREAD_DOT) {
207 getVariantsFromQualifier(refExpr, processor, qualifier);
210 getVariantsFromQualifierForSpreadOperator(refExpr, processor, qualifier);
214 GroovyResolveResult[] candidates = processor.getCandidates();
215 if (candidates.length == 0 && sameQualifier.length == 0) return PsiNamedElement.EMPTY_ARRAY;
216 candidates = filterStaticsOK(candidates);
217 PsiElement[] elements = ResolveUtil.mapToElements(candidates);
218 if (qualifier == null) {
219 List<GroovyResolveResult> nonPackages = ContainerUtil.findAll(candidates, new Condition<GroovyResolveResult>() {
220 public boolean value(final GroovyResolveResult result) {
221 return !(result.getElement() instanceof PsiPackage);
224 candidates = nonPackages.toArray(new GroovyResolveResult[nonPackages.size()]);
226 LookupElement[] propertyLookupElements = addPretendedProperties(elements);
227 Object[] variants = GroovyCompletionUtil.getCompletionVariants(candidates);
228 variants = ArrayUtil.mergeArrays(variants, propertyLookupElements, Object.class);
229 return ArrayUtil.mergeArrays(variants, sameQualifier, Object.class);
232 private static GroovyResolveResult[] filterStaticsOK(GroovyResolveResult[] candidates) {
233 List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>(candidates.length);
234 for (GroovyResolveResult resolveResult : candidates) {
235 if (resolveResult.isStaticsOK()) result.add(resolveResult);
237 return result.toArray(new GroovyResolveResult[result.size()]);
240 private static void getVariantsFromQualifierForSpreadOperator(GrReferenceExpression refExpr,
241 ResolverProcessor processor,
242 GrExpression qualifier) {
243 PsiType qualifierType = qualifier.getType();
244 if (qualifierType instanceof PsiClassType) {
245 PsiClassType.ClassResolveResult result = ((PsiClassType)qualifierType).resolveGenerics();
246 PsiClass clazz = result.getElement();
248 PsiClass listClass = JavaPsiFacade.getInstance(refExpr.getProject()).findClass("java.util.List", refExpr.getResolveScope());
249 if (listClass != null && listClass.getTypeParameters().length == 1) {
250 PsiSubstitutor substitutor = TypeConversionUtil.getClassSubstitutor(listClass, clazz, result.getSubstitutor());
251 if (substitutor != null) {
252 PsiType componentType = substitutor.substitute(listClass.getTypeParameters()[0]);
253 if (componentType != null) {
254 getVariantsFromQualifierType(refExpr, processor, componentType, refExpr.getProject());
260 else if (qualifierType instanceof PsiArrayType) {
261 getVariantsFromQualifierType(refExpr, processor, ((PsiArrayType)qualifierType).getComponentType(), refExpr.getProject());
265 private static LookupElement[] addPretendedProperties(PsiElement[] elements) {
266 List<LookupElement> result = new ArrayList<LookupElement>();
268 for (PsiElement element : elements) {
269 if (element instanceof PsiMethod && !(element instanceof GrAccessorMethod)) {
270 PsiMethod method = (PsiMethod)element;
271 if (GroovyPropertyUtils.isSimplePropertyAccessor(method)) {
272 String propName = GroovyPropertyUtils.getPropertyName(method);
273 if (!PsiUtil.isValidReferenceName(propName)) {
274 propName = "'" + propName + "'";
276 assert propName != null;
277 result.add(LookupElementBuilder.create(propName).setIcon(GroovyIcons.PROPERTY));
282 return result.toArray(new LookupElement[result.size()]);
285 private static void getVariantsFromQualifier(GrReferenceExpression refExpr, ResolverProcessor processor, GrExpression qualifier) {
286 Project project = qualifier.getProject();
287 PsiType qualifierType = qualifier.getType();
288 if (qualifierType == null) {
289 if (qualifier instanceof GrReferenceExpression) {
290 PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
291 if (resolved instanceof PsiPackage) {
292 resolved.processDeclarations(processor, ResolveState.initial(), null, refExpr);
296 final PsiClassType type = JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory()
297 .createTypeByFQClassName(GrTypeDefinition.DEFAULT_BASE_CLASS_NAME, refExpr.getResolveScope());
298 getVariantsFromQualifierType(refExpr, processor, type, project);
301 if (qualifierType instanceof PsiIntersectionType) {
302 for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
303 getVariantsFromQualifierType(refExpr, processor, conjunct, project);
307 getVariantsFromQualifierType(refExpr, processor, qualifierType, project);
308 if (qualifier instanceof GrReferenceExpression) {
309 PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
310 if (resolved instanceof PsiClass) { ////omitted .class
311 GlobalSearchScope scope = refExpr.getResolveScope();
312 PsiClass javaLangClass = PsiUtil.getJavaLangClass(resolved, scope);
313 if (javaLangClass != null) {
314 PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
315 PsiTypeParameter[] typeParameters = javaLangClass.getTypeParameters();
316 if (typeParameters.length == 1) {
317 substitutor = substitutor.put(typeParameters[0], qualifierType);
319 javaLangClass.processDeclarations(processor, ResolveState.initial(), null, refExpr);
320 PsiType javaLangClassType =
321 JavaPsiFacade.getInstance(refExpr.getProject()).getElementFactory().createType(javaLangClass, substitutor);
322 ResolveUtil.processNonCodeMethods(javaLangClassType, processor, refExpr.getProject(), refExpr, true);
330 private static String[] getVariantsWithSameQualifier(GrExpression qualifier, GrReferenceExpression refExpr) {
331 if (qualifier != null && qualifier.getType() != null) return ArrayUtil.EMPTY_STRING_ARRAY;
333 final PsiElement scope = PsiTreeUtil.getParentOfType(refExpr, GrMember.class, GroovyFileBase.class);
334 Set<String> result = new LinkedHashSet<String>();
335 addVariantsWithSameQualifier(scope, refExpr, qualifier, result);
336 return ArrayUtil.toStringArray(result);
339 private static void addVariantsWithSameQualifier(PsiElement element,
340 GrReferenceExpression patternExpression,
341 GrExpression patternQualifier,
342 Set<String> result) {
343 if (element instanceof GrReferenceExpression && element != patternExpression && !PsiUtil.isLValue((GroovyPsiElement)element)) {
344 final GrReferenceExpression refExpr = (GrReferenceExpression)element;
345 final String refName = refExpr.getReferenceName();
346 if (refName != null && !result.contains(refName)) {
347 final GrExpression hisQualifier = refExpr.getQualifierExpression();
348 if (hisQualifier != null && patternQualifier != null) {
349 if (PsiEquivalenceUtil.areElementsEquivalent(hisQualifier, patternQualifier)) {
350 if (refExpr.resolve() == null) {
355 else if (hisQualifier == null && patternQualifier == null) {
356 if (refExpr.resolve() == null) {
363 for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
364 addVariantsWithSameQualifier(child, patternExpression, patternQualifier, result);
368 private static void getVariantsFromQualifierType(GrReferenceExpression refExpr,
369 ResolverProcessor processor,
370 PsiType qualifierType,
372 if (qualifierType instanceof PsiClassType) {
373 PsiClass qualifierClass = ((PsiClassType)qualifierType).resolve();
374 if (qualifierClass != null) {
375 qualifierClass.processDeclarations(processor, ResolveState.initial(), null, refExpr);
377 if (!ResolveUtil.processCategoryMembers(refExpr, processor, (PsiClassType)qualifierType)) return;
379 else if (qualifierType instanceof PsiArrayType) {
380 final GrTypeDefinition arrayClass = GroovyPsiManager.getInstance(project).getArrayClass();
381 if (!arrayClass.processDeclarations(processor, ResolveState.initial(), null, refExpr)) return;
383 else if (qualifierType instanceof PsiIntersectionType) {
384 for (PsiType conjunct : ((PsiIntersectionType)qualifierType).getConjuncts()) {
385 getVariantsFromQualifierType(refExpr, processor, conjunct, project);
390 ResolveUtil.processNonCodeMethods(qualifierType, processor, project, refExpr, true);