Groovy type assignability
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / psi / impl / types / GrCodeReferenceElementImpl.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.lang.psi.impl.types;
18
19 import com.intellij.lang.ASTNode;
20 import com.intellij.psi.*;
21 import com.intellij.psi.impl.source.resolve.ResolveCache;
22 import com.intellij.psi.search.GlobalSearchScope;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import com.intellij.util.ArrayUtil;
25 import com.intellij.util.Consumer;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28 import org.jetbrains.plugins.groovy.lang.completion.GroovyCompletionUtil;
29 import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocReferenceElement;
30 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
31 import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
32 import org.jetbrains.plugins.groovy.lang.psi.GroovyElementVisitor;
33 import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
34 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
35 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrNewExpression;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
39 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
40 import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
41 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
42 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeArgumentList;
43 import org.jetbrains.plugins.groovy.lang.psi.impl.GrReferenceElementImpl;
44 import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyResolveResultImpl;
45 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
46 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
47 import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
48 import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassResolverProcessor;
49 import org.jetbrains.plugins.groovy.lang.resolve.processors.CompletionProcessor;
50 import org.jetbrains.plugins.groovy.lang.resolve.processors.ResolverProcessor;
51
52 import java.util.ArrayList;
53 import java.util.EnumSet;
54 import java.util.List;
55
56 import static org.jetbrains.plugins.groovy.lang.psi.impl.types.GrCodeReferenceElementImpl.ReferenceKind.*;
57
58 /**
59  * @author: Dmitry.Krasilschikov
60  * @date: 26.03.2007
61  */
62 public class GrCodeReferenceElementImpl extends GrReferenceElementImpl implements GrCodeReferenceElement {
63   public GrCodeReferenceElementImpl(@NotNull ASTNode node) {
64     super(node);
65   }
66
67   @Override
68   protected PsiElement bindWithQualifiedRef(String qName) {
69     final GrTypeArgumentList list = getTypeArgumentList();
70     final String typeArgs = (list != null) ? list.getText() : "";
71     final String text = qName + typeArgs;
72     final GrCodeReferenceElement qualifiedRef = GroovyPsiElementFactory.getInstance(getProject()).createTypeOrPackageReference(text);
73     getNode().getTreeParent().replaceChild(getNode(), qualifiedRef.getNode());
74     PsiUtil.shortenReference(qualifiedRef);
75     return qualifiedRef;
76   }
77
78   public void accept(GroovyElementVisitor visitor) {
79     visitor.visitCodeReferenceElement(this);
80   }
81
82   public String toString() {
83     return "Reference element";
84   }
85
86   public GrCodeReferenceElement getQualifier() {
87     return (GrCodeReferenceElement) findChildByType(GroovyElementTypes.REFERENCE_ELEMENT);
88   }
89
90   public void setQualifier(@Nullable GrCodeReferenceElement newQualifier) {
91     final GrCodeReferenceElement qualifier = getQualifier();
92     if (newQualifier == null) {
93       if (qualifier == null) return;
94       getNode().removeRange(getNode().getFirstChildNode(), getReferenceNameElement().getNode());
95     } else {
96       if (qualifier == null) {
97         final ASTNode refNameNode = getReferenceNameElement().getNode();
98         getNode().addChild(newQualifier.getNode(), refNameNode);
99         getNode().addLeaf(GroovyTokenTypes.mDOT, ".", refNameNode);
100       } else {
101         getNode().replaceChild(qualifier.getNode(), newQualifier.getNode());
102       }
103     }
104   }
105
106   enum ReferenceKind {
107     CLASS,
108     CLASS_OR_PACKAGE,
109     PACKAGE_FQ,
110     CLASS_FQ,
111     CLASS_OR_PACKAGE_FQ,
112     STATIC_MEMBER_FQ,
113     CLASS_IN_QUALIFIED_NEW
114   }
115
116   @Nullable
117   public PsiElement resolve() {
118     ResolveResult[] results = getManager().getResolveCache().resolveWithCaching(this, RESOLVER, true, false);
119     return results.length == 1 ? results[0].getElement() : null;
120   }
121
122   private ReferenceKind getKind(boolean forCompletion) {
123     if (isClassReferenceForNew()) {
124       return CLASS_OR_PACKAGE;
125     }
126
127     PsiElement parent = getParent();
128     if (parent instanceof GrCodeReferenceElement) {
129       ReferenceKind parentKind = ((GrCodeReferenceElementImpl) parent).getKind(forCompletion);
130       if (parentKind == CLASS) return CLASS_OR_PACKAGE;
131       else if (parentKind == STATIC_MEMBER_FQ) return CLASS_FQ;
132       else if (parentKind == CLASS_FQ) return CLASS_OR_PACKAGE_FQ;
133       return parentKind;
134     } else if (parent instanceof GrPackageDefinition) {
135       return PACKAGE_FQ;
136     } else if (parent instanceof GrDocReferenceElement) {
137       return CLASS_OR_PACKAGE;
138     } else if (parent instanceof GrImportStatement) {
139       final GrImportStatement importStatement = (GrImportStatement) parent;
140       if (importStatement.isStatic()) {
141         return importStatement.isOnDemand() ? CLASS_FQ : STATIC_MEMBER_FQ;
142       }
143       else {
144         return forCompletion || importStatement.isOnDemand() ? CLASS_OR_PACKAGE_FQ : CLASS_FQ;
145       }
146     }
147     else if (parent instanceof GrNewExpression || parent instanceof GrAnonymousClassDefinition) {
148       if (parent instanceof GrAnonymousClassDefinition) {
149         parent = parent.getParent();
150       }
151       assert parent instanceof GrNewExpression;
152       final GrNewExpression newExpression = (GrNewExpression)parent;
153       if (newExpression.getQualifier() != null) return CLASS_IN_QUALIFIED_NEW;
154     }
155
156     return CLASS;
157   }
158
159   @Nullable
160   public String getCanonicalText() {
161     PsiElement resolved = resolve();
162     if (resolved instanceof PsiClass) {
163       return ((PsiClass) resolved).getQualifiedName();
164     }
165     if (resolved instanceof PsiPackage) {
166       return ((PsiPackage) resolved).getQualifiedName();
167     }
168     if (getKind(false) == STATIC_MEMBER_FQ) {
169       final GrCodeReferenceElement qualifier = getQualifier();
170       if (qualifier != null) {
171         final String qualifierText = qualifier.getCanonicalText();
172         if (qualifierText != null) {
173           return qualifierText + "." + getReferenceName();
174         }
175       }
176     }
177
178     return null;
179   }
180
181   protected boolean bindsCorrectly(PsiElement element) {
182     if (super.bindsCorrectly(element)) return true;
183     if (element instanceof PsiClass) {
184       final PsiElement resolved = resolve();
185       if (resolved instanceof PsiMethod) {
186         final PsiMethod method = (PsiMethod) resolved;
187         if (method.isConstructor() && getManager().areElementsEquivalent(element, method.getContainingClass())) {
188           return true;
189         }
190       }
191     }
192
193     return false;
194   }
195
196   public boolean isReferenceTo(PsiElement element) {
197     return getManager().areElementsEquivalent(element, resolve());
198   }
199
200   @NotNull
201   public Object[] getVariants() {
202     return ArrayUtil.EMPTY_OBJECT_ARRAY;
203   }
204
205   private boolean isClassReferenceForNew() {
206     PsiElement parent = getParent();
207     while (parent instanceof GrCodeReferenceElement) parent = parent.getParent();
208     return parent instanceof GrNewExpression;
209   }
210
211   private void processVariantsImpl(ReferenceKind kind, Consumer<Object> consumer) {
212     switch (kind) {
213       case STATIC_MEMBER_FQ: {
214         final GrCodeReferenceElement qualifier = getQualifier();
215         if (qualifier != null) {
216           final PsiElement resolve = qualifier.resolve();
217           if (resolve instanceof PsiClass) {
218             final PsiClass clazz = (PsiClass) resolve;
219
220             for (PsiField field : clazz.getFields()) {
221               if (field.hasModifierProperty(PsiModifier.STATIC)) {
222                 consumer.consume(field);
223               }
224             }
225
226             for (PsiMethod method : clazz.getMethods()) {
227               if (method.hasModifierProperty(PsiModifier.STATIC)) {
228                 consumer.consume(method);
229               }
230             }
231             return;
232           }
233         }
234       }
235       //fallthrough
236
237       case PACKAGE_FQ:
238       case CLASS_FQ:
239       case CLASS_OR_PACKAGE_FQ: {
240         final String refText = PsiUtil.getQualifiedReferenceText(this);
241         final int lastDot = refText.lastIndexOf(".");
242         String parentPackageFQName = lastDot > 0 ? refText.substring(0, lastDot) : "";
243         final PsiPackage parentPackage = JavaPsiFacade.getInstance(getProject()).findPackage(parentPackageFQName);
244         if (parentPackage != null) {
245           final GlobalSearchScope scope = getResolveScope();
246           if (kind == PACKAGE_FQ) {
247             for (PsiPackage aPackage : parentPackage.getSubPackages(scope)) {
248               consumer.consume(aPackage);
249             }
250             return;
251           } else {
252             if (kind == CLASS_FQ) {
253               for (PsiClass aClass : parentPackage.getClasses(scope)) {
254                 consumer.consume(aClass);
255               }
256               return;
257             } else {
258               final PsiPackage[] subpackages = parentPackage.getSubPackages(scope);
259               final PsiClass[] classes = parentPackage.getClasses(scope);
260               for (PsiPackage aPackage : subpackages) {
261                 consumer.consume(aPackage);
262               }
263               for (PsiClass aClass : classes) {
264                 consumer.consume(aClass);
265               }
266               return;
267             }
268           }
269         }
270       }
271
272       case CLASS_OR_PACKAGE:
273       case CLASS_IN_QUALIFIED_NEW:
274       case CLASS: {
275         GrCodeReferenceElement qualifier = getQualifier();
276         if (qualifier != null) {
277           PsiElement qualifierResolved = qualifier.resolve();
278           if (qualifierResolved instanceof PsiPackage) {
279             PsiPackage aPackage = (PsiPackage) qualifierResolved;
280             PsiClass[] classes = aPackage.getClasses(getResolveScope());
281
282             for (PsiClass aClass : classes) {
283               consumer.consume(aClass);
284             }
285             if (kind == CLASS) return;
286
287             PsiPackage[] subpackages = aPackage.getSubPackages();
288             for (PsiPackage subpackage : subpackages) {
289               consumer.consume(subpackage);
290             }
291           } else if (qualifierResolved instanceof PsiClass) {
292             for (PsiClass aClass : ((PsiClass)qualifierResolved).getInnerClasses()) {
293               consumer.consume(aClass);
294             }
295           }
296         } else {
297           ResolverProcessor classProcessor = CompletionProcessor.createClassCompletionProcessor(this);
298           ResolveUtil.treeWalkUp(this, classProcessor, false);
299
300           for (Object o : GroovyCompletionUtil.getCompletionVariants(classProcessor.getCandidates())) {
301             consumer.consume(o);
302           }
303         }
304       }
305     }
306   }
307
308   public boolean isSoft() {
309     return false;
310   }
311
312   private static class OurResolver implements ResolveCache.PolyVariantResolver<GrCodeReferenceElementImpl> {
313
314     public GroovyResolveResult[] resolve(GrCodeReferenceElementImpl reference, boolean incompleteCode) {
315       if (reference.getReferenceName() == null) return GroovyResolveResult.EMPTY_ARRAY;
316       final GroovyResolveResult[] results = _resolve(reference, reference.getManager(), reference.getKind(false));
317       final PsiType[] args = reference.getTypeArguments();
318       for (int i = 0; i < results.length; i++) {
319         GroovyResolveResult result = results[i];
320         final PsiElement element = result.getElement();
321         if (element instanceof PsiClass) {
322           final PsiSubstitutor substitutor = result.getSubstitutor();
323           final PsiSubstitutor newSubstitutor = substitutor.putAll((PsiClass) element, args);
324           results[i] = new GroovyResolveResultImpl(element, result.getCurrentFileResolveContext(), newSubstitutor, result.isAccessible(), result.isStaticsOK());
325         }
326       }
327       return results;
328     }
329
330     private static GroovyResolveResult[] _resolve(GrCodeReferenceElementImpl ref, PsiManager manager,
331                                            ReferenceKind kind) {
332       final String refName = ref.getReferenceName();
333       if (refName == null) {
334         return GroovyResolveResult.EMPTY_ARRAY;
335       }
336
337       switch (kind) {
338         case CLASS_OR_PACKAGE_FQ:
339         case CLASS_FQ:
340         case PACKAGE_FQ:
341           String qName = PsiUtil.getQualifiedReferenceText(ref);
342
343           JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
344           if (kind == CLASS_OR_PACKAGE_FQ || kind == CLASS_FQ) {
345             final PsiFile file = ref.getContainingFile();
346             if (qName.indexOf('.') > 0 || file instanceof GroovyFile && ((GroovyFile)file).getPackageName().length() == 0) {
347               PsiClass aClass = facade.findClass(qName, ref.getResolveScope());
348               if (aClass != null) {
349                 boolean isAccessible = PsiUtil.isAccessible(ref, aClass);
350                 return new GroovyResolveResult[]{new GroovyResolveResultImpl(aClass, isAccessible)};
351               }
352             }
353           }
354
355           if (kind == CLASS_OR_PACKAGE_FQ || kind == PACKAGE_FQ) {
356             PsiPackage aPackage = facade.findPackage(qName);
357             if (aPackage != null) {
358               return new GroovyResolveResult[]{new GroovyResolveResultImpl(aPackage, true)};
359             }
360           }
361
362           break;
363
364         case CLASS:
365         case CLASS_OR_PACKAGE: {
366           GrCodeReferenceElement qualifier = ref.getQualifier();
367           if (qualifier != null) {
368             PsiElement qualifierResolved = qualifier.resolve();
369             if (qualifierResolved instanceof PsiPackage) {
370               for (final PsiClass aClass : ((PsiPackage) qualifierResolved).getClasses(ref.getResolveScope())) {
371                 if (refName.equals(aClass.getName())) {
372                   boolean isAccessible = PsiUtil.isAccessible(ref, aClass);
373                   return new GroovyResolveResult[]{new GroovyResolveResultImpl(aClass, isAccessible)};
374                 }
375               }
376
377               if (kind == CLASS_OR_PACKAGE) {
378                 for (final PsiPackage subpackage : ((PsiPackage) qualifierResolved).getSubPackages()) {
379                   if (refName.equals(subpackage.getName()))
380                     return new GroovyResolveResult[]{new GroovyResolveResultImpl(subpackage, true)};
381                 }
382               }
383             } else if ((kind == CLASS || kind == CLASS_OR_PACKAGE) && qualifierResolved instanceof PsiClass) {
384               PsiClass[] classes = ((PsiClass) qualifierResolved).getAllInnerClasses();
385               for (final PsiClass aClass : classes) {
386                 if (refName.equals(aClass.getName())) {
387                   boolean isAccessible = PsiUtil.isAccessible(ref, aClass);
388                   return new GroovyResolveResult[]{new GroovyResolveResultImpl(aClass, isAccessible)};
389                 }
390               }
391             }
392           } else {
393             EnumSet<ClassHint.ResolveKind> kinds = kind == CLASS ? EnumSet.of(ClassHint.ResolveKind.CLASS) :
394                 EnumSet.of(ClassHint.ResolveKind.PACKAGE, ClassHint.ResolveKind.CLASS);
395             ResolverProcessor processor = new ClassResolverProcessor(refName, ref, kinds);
396             ResolveUtil.treeWalkUp(ref, processor, false);
397             GroovyResolveResult[] candidates = processor.getCandidates();
398             if (candidates.length > 0) return candidates;
399
400             if (kind == CLASS_OR_PACKAGE) {
401               PsiPackage defaultPackage = JavaPsiFacade.getInstance(ref.getProject()).findPackage("");
402               if (defaultPackage != null) {
403                 for (final PsiPackage subpackage : defaultPackage.getSubPackages()) {
404                   if (refName.equals(subpackage.getName()))
405                     return new GroovyResolveResult[]{new GroovyResolveResultImpl(subpackage, true)};
406                 }
407               }
408             }
409           }
410
411           break;
412         }
413
414         case STATIC_MEMBER_FQ: {
415           final GrCodeReferenceElement qualifier = ref.getQualifier();
416           if (qualifier != null) {
417             final PsiElement resolve = qualifier.resolve();
418             if (resolve instanceof PsiClass) {
419               final PsiClass clazz = (PsiClass) resolve;
420               PsiResolveHelper helper = JavaPsiFacade.getInstance(clazz.getProject()).getResolveHelper();
421               List<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
422               final PsiField field = clazz.findFieldByName(refName, false);
423               if (field != null && field.hasModifierProperty(PsiModifier.STATIC)) {
424                 result.add(new GroovyResolveResultImpl(field, helper.isAccessible(field, ref, null)));
425               }
426
427               final PsiMethod[] methods = clazz.findMethodsByName(refName, false);
428               for (PsiMethod method : methods) {
429                 result.add(new GroovyResolveResultImpl(method, helper.isAccessible(method, ref, null)));
430               }
431
432               return result.toArray(new GroovyResolveResult[result.size()]);
433             }
434           }
435           break;
436         }
437         case CLASS_IN_QUALIFIED_NEW: {
438           if (ref.getParent() instanceof GrCodeReferenceElement) return GroovyResolveResult.EMPTY_ARRAY;
439           final GrNewExpression newExpression = PsiTreeUtil.getParentOfType(ref, GrNewExpression.class);
440           assert newExpression != null;
441           final GrExpression qualifier = newExpression.getQualifier();
442           assert qualifier != null;
443
444           final PsiType type = qualifier.getType();
445           if (!(type instanceof PsiClassType)) break;
446
447           final PsiClassType classType = (PsiClassType)type;
448           final PsiClass psiClass = classType.resolve();
449           if (psiClass == null) break;
450
451           final PsiClass[] allInnerClasses = psiClass.getAllInnerClasses();
452           ArrayList<GroovyResolveResult> result = new ArrayList<GroovyResolveResult>();
453           PsiResolveHelper helper = JavaPsiFacade.getInstance(ref.getProject()).getResolveHelper();
454
455           for (final PsiClass innerClass : allInnerClasses) {
456             if (refName.equals(innerClass.getName())) {
457               result.add(new GroovyResolveResultImpl(innerClass, helper.isAccessible(innerClass, ref, null)));
458             }
459           }
460           return result.toArray(new GroovyResolveResult[result.size()]);
461         }
462       }
463
464       return GroovyResolveResult.EMPTY_ARRAY;
465     }
466   }
467
468   private static final OurResolver RESOLVER = new OurResolver();
469
470   public GroovyResolveResult advancedResolve() {
471     ResolveResult[] results = getManager().getResolveCache().resolveWithCaching(this, RESOLVER, true, false);
472     return results.length == 1 ? (GroovyResolveResult) results[0] : GroovyResolveResult.EMPTY_RESULT;
473   }
474
475   @NotNull
476   public GroovyResolveResult[] multiResolve(boolean incompleteCode) {
477     final ResolveResult[] results = getManager().getResolveCache().resolveWithCaching(this, RESOLVER, true, incompleteCode);
478     if (results.length == 0) {
479       return GroovyResolveResult.EMPTY_ARRAY;
480     }
481
482     return (GroovyResolveResult[])results;
483   }
484
485   public void processVariants(Consumer<Object> consumer) {
486     processVariantsImpl(getKind(true), consumer);
487   }
488 }