Merge branch 'alias'
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / psi / util / GrClassImplUtil.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.util;
18
19 import com.intellij.openapi.util.Condition;
20 import com.intellij.openapi.util.Pair;
21 import com.intellij.psi.*;
22 import com.intellij.psi.impl.PsiClassImplUtil;
23 import com.intellij.psi.infos.CandidateInfo;
24 import com.intellij.psi.scope.NameHint;
25 import com.intellij.psi.scope.PsiScopeProcessor;
26 import com.intellij.psi.util.MethodSignature;
27 import com.intellij.psi.util.TypeConversionUtil;
28 import com.intellij.util.ArrayUtil;
29 import com.intellij.util.Function;
30 import com.intellij.util.containers.ContainerUtil;
31 import org.jetbrains.annotations.NonNls;
32 import org.jetbrains.annotations.NotNull;
33 import org.jetbrains.annotations.Nullable;
34 import org.jetbrains.plugins.groovy.GroovyFileType;
35 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
36 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
37 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
38 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
39 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
40 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrAnonymousClassDefinition;
41 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrReferenceList;
42 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
43 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody;
44 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod;
45 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
46 import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
47 import org.jetbrains.plugins.groovy.lang.psi.impl.GrClassReferenceType;
48 import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
49 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
50 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.typedef.GrTypeDefinitionImpl;
51 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrSyntheticMethodImplementation;
52 import org.jetbrains.plugins.groovy.lang.resolve.CollectClassMembersUtil;
53 import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
54 import org.jetbrains.plugins.groovy.lang.resolve.processors.ClassHint;
55
56 import java.util.*;
57
58 /**
59  * @author Maxim.Medvedev
60  */
61 public class GrClassImplUtil {
62   private static final Condition<PsiClassType> IS_GROOVY_OBJECT = new Condition<PsiClassType>() {
63     public boolean value(PsiClassType psiClassType) {
64       return psiClassType.equalsToText(GrTypeDefinition.DEFAULT_BASE_CLASS_NAME);
65     }
66   };
67
68   private GrClassImplUtil() {
69   }
70
71
72   @Nullable
73   public static PsiClass getSuperClass(GrTypeDefinition grType) {
74     final PsiClassType[] extendsList = grType.getExtendsListTypes();
75     if (extendsList.length == 0) return getBaseClass(grType);
76     final PsiClass superClass = extendsList[0].resolve();
77     return superClass != null ? superClass : getBaseClass(grType);
78   }
79
80   @Nullable
81   public static PsiClass getBaseClass(GrTypeDefinition grType) {
82     if (grType.isEnum()) {
83       return JavaPsiFacade.getInstance(grType.getProject()).findClass(CommonClassNames.JAVA_LANG_ENUM, grType.getResolveScope());
84     }
85     else {
86       return JavaPsiFacade.getInstance(grType.getProject()).findClass(CommonClassNames.JAVA_LANG_OBJECT, grType.getResolveScope());
87     }
88   }
89
90   @NotNull
91   public static PsiClassType[] getExtendsListTypes(GrTypeDefinition grType) {
92     final List<PsiClassType> extendsTypes = getReferenceListTypes(grType.getExtendsClause());
93     return extendsTypes.toArray(new PsiClassType[extendsTypes.size()]);
94   }
95
96   @NotNull
97   public static PsiClassType[] getImplementsListTypes(GrTypeDefinition grType) {
98     final List<PsiClassType> implementsTypes = getReferenceListTypes(grType.getImplementsClause());
99     if (!grType.isInterface() &&
100         !ContainerUtil.or(implementsTypes, IS_GROOVY_OBJECT) &&
101         !ContainerUtil.or(getReferenceListTypes(grType.getExtendsClause()), IS_GROOVY_OBJECT)) {
102       implementsTypes.add(getGroovyObjectType(grType));
103     }
104     return implementsTypes.toArray(new PsiClassType[implementsTypes.size()]);
105   }
106
107   private static PsiClassType getGroovyObjectType(GrTypeDefinition grType) {
108     return JavaPsiFacade.getInstance(grType.getProject()).getElementFactory()
109       .createTypeByFQClassName(GrTypeDefinition.DEFAULT_BASE_CLASS_NAME, grType.getResolveScope());
110   }
111
112   @NotNull
113   public static PsiClassType[] getSuperTypes(GrTypeDefinition grType) {
114     PsiClassType[] extendsList = grType.getExtendsListTypes();
115     if (extendsList.length == 0) {
116       extendsList = new PsiClassType[]{createBaseClassType(grType)};
117     }
118
119     return ArrayUtil.mergeArrays(extendsList, grType.getImplementsListTypes(), PsiClassType.class);
120   }
121
122   public static PsiClassType createBaseClassType(GrTypeDefinition grType) {
123     if (grType.isEnum()) {
124       return JavaPsiFacade.getInstance(grType.getProject()).getElementFactory()
125         .createTypeByFQClassName(CommonClassNames.JAVA_LANG_ENUM, grType.getResolveScope());
126     }
127     else {
128       return JavaPsiFacade.getInstance(grType.getProject()).getElementFactory()
129         .createTypeByFQClassName(CommonClassNames.JAVA_LANG_OBJECT, grType.getResolveScope());
130     }
131   }
132
133   @NotNull
134   public static PsiMethod[] getAllMethods(GrTypeDefinition grType) {
135     List<PsiMethod> allMethods = new ArrayList<PsiMethod>();
136     getAllMethodsInner(grType, allMethods, new HashSet<PsiClass>());
137
138     return allMethods.toArray(new PsiMethod[allMethods.size()]);
139   }
140
141   private static void getAllMethodsInner(PsiClass clazz, List<PsiMethod> allMethods, HashSet<PsiClass> visited) {
142     if (visited.contains(clazz)) return;
143     visited.add(clazz);
144
145     allMethods.addAll(Arrays.asList(clazz.getMethods()));
146
147     final PsiField[] fields = clazz.getFields();
148     for (PsiField field : fields) {
149       if (field instanceof GrField) {
150         final GrField groovyField = (GrField)field;
151         if (groovyField.isProperty()) {
152           PsiMethod[] getters = groovyField.getGetters();
153           if (getters.length > 0) allMethods.addAll(Arrays.asList(getters));
154           PsiMethod setter = groovyField.getSetter();
155           if (setter != null) allMethods.add(setter);
156         }
157       }
158     }
159
160     final PsiClass[] supers = clazz.getSupers();
161     if (supers.length < 2) {
162       addGroovyObjectMethods(clazz, allMethods);
163     }
164     for (PsiClass aSuper : supers) {
165       getAllMethodsInner(aSuper, allMethods, visited);
166     }
167   }
168
169   public static void addGroovyObjectMethods(PsiClass clazz, List<PsiMethod> allMethods) {
170     if (clazz instanceof GrTypeDefinition && !clazz.isInterface() /*&& clazz.getExtendsListTypes().length == 0*/) {
171       final PsiClass groovyObject =
172         JavaPsiFacade.getInstance(clazz.getProject()).findClass(GrTypeDefinition.DEFAULT_BASE_CLASS_NAME, clazz.getResolveScope());
173       if (groovyObject != null) {
174         for (final PsiMethod method : groovyObject.getMethods()) {
175           allMethods.add(new GrSyntheticMethodImplementation(method, clazz));
176         }
177       }
178     }
179   }
180
181
182   private static List<PsiClassType> getReferenceListTypes(@Nullable GrReferenceList list) {
183     final ArrayList<PsiClassType> types = new ArrayList<PsiClassType>();
184     if (list != null) {
185       for (GrCodeReferenceElement ref : list.getReferenceElements()) {
186         types.add(new GrClassReferenceType(ref));
187       }
188     }
189     return types;
190   }
191
192
193   public static PsiClass[] getInterfaces(GrTypeDefinition grType) {
194     final PsiClassType[] implementsListTypes = grType.getImplementsListTypes();
195     List<PsiClass> result = new ArrayList<PsiClass>(implementsListTypes.length);
196     for (PsiClassType type : implementsListTypes) {
197       final PsiClass psiClass = type.resolve();
198       if (psiClass != null) result.add(psiClass);
199     }
200     return result.toArray(new PsiClass[result.size()]);
201   }
202
203   @NotNull
204   public static PsiClass[] getSupers(GrTypeDefinition grType) {
205     PsiClassType[] superTypes = grType.getSuperTypes();
206     List<PsiClass> result = new ArrayList<PsiClass>();
207     for (PsiClassType superType : superTypes) {
208       PsiClass superClass = superType.resolve();
209       if (superClass != null) {
210         result.add(superClass);
211       }
212     }
213
214     return result.toArray(new PsiClass[result.size()]);
215   }
216
217   public static boolean processDeclarations(@NotNull GrTypeDefinition grType,
218                                             @NotNull PsiScopeProcessor processor,
219                                             @NotNull ResolveState state,
220                                             PsiElement lastParent,
221                                             @NotNull PsiElement place) {
222     for (final PsiTypeParameter typeParameter : grType.getTypeParameters()) {
223       if (!ResolveUtil.processElement(processor, typeParameter)) return false;
224     }
225
226     NameHint nameHint = processor.getHint(NameHint.KEY);
227     //todo [DIANA] look more carefully
228     String name = nameHint == null ? null : nameHint.getName(state);
229     ClassHint classHint = processor.getHint(ClassHint.KEY);
230     final PsiSubstitutor substitutor = state.get(PsiSubstitutor.KEY);
231     final PsiElementFactory factory = JavaPsiFacade.getElementFactory(place.getProject());
232
233     if (classHint == null || classHint.shouldProcess(ClassHint.ResolveKind.PROPERTY)) {
234       Map<String, CandidateInfo> fieldsMap = CollectClassMembersUtil.getAllFields(grType);
235       if (name != null) {
236         CandidateInfo fieldInfo = fieldsMap.get(name);
237         if (fieldInfo != null) {
238           final PsiField field = (PsiField)fieldInfo.getElement();
239           if (!isSameDeclaration(place, field)) { //the same variable declaration
240             final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
241               .obtainFinalSubstitutor(field.getContainingClass(), fieldInfo.getSubstitutor(), grType, substitutor, place, factory);
242             if (!processor.execute(field, ResolveState.initial().put(PsiSubstitutor.KEY, finalSubstitutor))) return false;
243           }
244         }
245       }
246       else {
247         for (CandidateInfo info : fieldsMap.values()) {
248           final PsiField field = (PsiField)info.getElement();
249           if (!isSameDeclaration(place, field)) {  //the same variable declaration
250             final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
251               .obtainFinalSubstitutor(field.getContainingClass(), info.getSubstitutor(), grType, substitutor, place, factory);
252             if (!processor.execute(field, ResolveState.initial().put(PsiSubstitutor.KEY, finalSubstitutor))) return false;
253           }
254         }
255       }
256     }
257
258     if (classHint == null || classHint.shouldProcess(ClassHint.ResolveKind.METHOD)) {
259       Map<String, List<CandidateInfo>> methodsMap = CollectClassMembersUtil.getAllMethods(grType, true);
260       boolean isPlaceGroovy = place.getLanguage() == GroovyFileType.GROOVY_FILE_TYPE.getLanguage();
261       if (name == null) {
262         for (List<CandidateInfo> list : methodsMap.values()) {
263           for (CandidateInfo info : list) {
264             PsiMethod method = (PsiMethod)info.getElement();
265             if (!isSameDeclaration(place, method) && isMethodVisible(isPlaceGroovy, method)) {
266               final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
267                 .obtainFinalSubstitutor(method.getContainingClass(), info.getSubstitutor(), grType, substitutor, place, factory);
268               if (!processor.execute(method, ResolveState.initial().put(PsiSubstitutor.KEY, finalSubstitutor))) {
269                 return false;
270               }
271             }
272           }
273         }
274       }
275       else {
276         List<CandidateInfo> byName = methodsMap.get(name);
277         if (byName != null) {
278           for (CandidateInfo info : byName) {
279             PsiMethod method = (PsiMethod)info.getElement();
280             if (!isSameDeclaration(place, method) && isMethodVisible(isPlaceGroovy, method)) {
281               final PsiSubstitutor finalSubstitutor = PsiClassImplUtil
282                 .obtainFinalSubstitutor(method.getContainingClass(), info.getSubstitutor(), grType, substitutor, place, factory);
283               if (!processor.execute(method, ResolveState.initial().put(PsiSubstitutor.KEY, finalSubstitutor))) {
284                 return false;
285               }
286             }
287           }
288         }
289       }
290     }
291
292     final GrTypeDefinitionBody body = grType.getBody();
293     if (body != null && !isSuperClassReferenceResolving(grType, lastParent)) {
294       if (classHint == null || classHint.shouldProcess(ClassHint.ResolveKind.CLASS)) {
295         for (CandidateInfo info : CollectClassMembersUtil.getAllInnerClasses(grType, false).values()) {
296           final PsiClass innerClass = (PsiClass)info.getElement();
297           assert innerClass != null;
298           final String innerClassName = innerClass.getName();
299           if (nameHint != null && !innerClassName.equals(nameHint.getName(state))) {
300             continue;
301           }
302
303           if (!processor.execute(innerClass, state)) {
304             return false;
305           }
306         }
307       }
308     }
309
310
311     return true;
312   }
313
314   private static boolean isSuperClassReferenceResolving(GrTypeDefinition grType, PsiElement lastParent) {
315     return lastParent instanceof GrReferenceList ||
316            grType.isAnonymous() && lastParent == ((GrAnonymousClassDefinition)grType).getBaseClassReferenceGroovy();
317   }
318
319
320   private static boolean isSameDeclaration(PsiElement place, PsiElement element) {
321     if (element instanceof GrAccessorMethod) element = ((GrAccessorMethod)element).getProperty();
322
323     if (!(element instanceof GrField)) return false;
324     while (place != null) {
325       place = place.getParent();
326       if (place == element) return true;
327       if (place instanceof GrClosableBlock) return false;
328     }
329     return false;
330   }
331
332   private static boolean isMethodVisible(boolean isPlaceGroovy, PsiMethod method) {
333     return isPlaceGroovy || !(method instanceof GrGdkMethod);
334   }
335
336   @Nullable
337   public static PsiMethod findMethodBySignature(GrTypeDefinition grType, PsiMethod patternMethod, boolean checkBases) {
338     final MethodSignature patternSignature = patternMethod.getSignature(PsiSubstitutor.EMPTY);
339     for (PsiMethod method : findMethodsByName(grType, patternMethod.getName(), checkBases, false)) {
340       final PsiClass clazz = method.getContainingClass();
341       if (clazz == null) continue;
342       PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(clazz, grType, PsiSubstitutor.EMPTY);
343       if (superSubstitutor == null) continue;
344       final MethodSignature signature = method.getSignature(superSubstitutor);
345       if (signature.equals(patternSignature)) return method;
346     }
347
348     return null;
349   }
350
351   private static PsiMethod[] findMethodsByName(GrTypeDefinition grType,
352                                                String name,
353                                                boolean checkBases,
354                                                boolean includeSyntheticAccessors) {
355     if (!checkBases) {
356       List<PsiMethod> result = new ArrayList<PsiMethod>();
357       for (PsiMethod method : includeSyntheticAccessors ? grType.getMethods() : grType.getGroovyMethods()) {
358         if (name.equals(method.getName())) result.add(method);
359       }
360
361       return result.toArray(new PsiMethod[result.size()]);
362     }
363
364     Map<String, List<CandidateInfo>> methodsMap = CollectClassMembersUtil.getAllMethods(grType, includeSyntheticAccessors);
365     return PsiImplUtil.mapToMethods(methodsMap.get(name));
366   }
367
368   @NotNull
369   public static PsiMethod[] findMethodsBySignature(GrTypeDefinition grType, PsiMethod patternMethod, boolean checkBases) {
370     return findMethodsBySignature(grType, patternMethod, checkBases, true);
371   }
372
373   @NotNull
374   public static PsiMethod[] findCodeMethodsBySignature(GrTypeDefinition grType, PsiMethod patternMethod, boolean checkBases) {
375     return findMethodsBySignature(grType, patternMethod, checkBases, false);
376   }
377
378   @NotNull
379   public static PsiMethod[] findMethodsByName(GrTypeDefinition grType, @NonNls String name, boolean checkBases) {
380     return findMethodsByName(grType, name, checkBases, true);
381   }
382
383   private static PsiMethod[] findMethodsBySignature(GrTypeDefinition grType,
384                                                     PsiMethod patternMethod,
385                                                     boolean checkBases,
386                                                     boolean includeSynthetic) {
387     ArrayList<PsiMethod> result = new ArrayList<PsiMethod>();
388     final MethodSignature patternSignature = patternMethod.getSignature(PsiSubstitutor.EMPTY);
389     for (PsiMethod method : findMethodsByName(grType, patternMethod.getName(), checkBases, includeSynthetic)) {
390       final PsiClass clazz = method.getContainingClass();
391       if (clazz == null) continue;
392       PsiSubstitutor superSubstitutor = TypeConversionUtil.getClassSubstitutor(clazz, grType, PsiSubstitutor.EMPTY);
393       if (superSubstitutor == null) continue;
394
395       final MethodSignature signature = method.getSignature(superSubstitutor);
396       if (signature.equals(patternSignature)) //noinspection unchecked
397       {
398         result.add(method);
399       }
400     }
401     return result.toArray(new PsiMethod[result.size()]);
402   }
403
404   @NotNull
405   public static PsiMethod[] findCodeMethodsByName(GrTypeDefinition grType, @NonNls String name, boolean checkBases) {
406     return findMethodsByName(grType, name, checkBases, false);
407   }
408
409   @NotNull
410   public static List<Pair<PsiMethod, PsiSubstitutor>> findMethodsAndTheirSubstitutorsByName(GrTypeDefinition grType,
411                                                                                             String name,
412                                                                                             boolean checkBases) {
413     final ArrayList<Pair<PsiMethod, PsiSubstitutor>> result = new ArrayList<Pair<PsiMethod, PsiSubstitutor>>();
414
415     if (!checkBases) {
416       final PsiMethod[] methods = grType.findMethodsByName( name, false);
417       for (PsiMethod method : methods) {
418         result.add(new Pair<PsiMethod, PsiSubstitutor>(method, PsiSubstitutor.EMPTY));
419       }
420     }
421     else {
422       final Map<String, List<CandidateInfo>> map = CollectClassMembersUtil.getAllMethods(grType, true);
423       final List<CandidateInfo> candidateInfos = map.get(name);
424       if (candidateInfos != null) {
425         for (CandidateInfo info : candidateInfos) {
426           final PsiElement element = info.getElement();
427           result.add(new Pair<PsiMethod, PsiSubstitutor>((PsiMethod)element, info.getSubstitutor()));
428         }
429       }
430     }
431
432     return result;
433   }
434
435   @NotNull
436   public static List<Pair<PsiMethod, PsiSubstitutor>> getAllMethodsAndTheirSubstitutors(GrTypeDefinition grType) {
437     final Map<String, List<CandidateInfo>> allMethodsMap = CollectClassMembersUtil.getAllMethods(grType, true);
438     List<Pair<PsiMethod, PsiSubstitutor>> result = new ArrayList<Pair<PsiMethod, PsiSubstitutor>>();
439     for (List<CandidateInfo> infos : allMethodsMap.values()) {
440       for (CandidateInfo info : infos) {
441         result.add(new Pair<PsiMethod, PsiSubstitutor>((PsiMethod)info.getElement(), info.getSubstitutor()));
442       }
443     }
444
445     return result;
446   }
447
448   @Nullable
449   public static PsiField findFieldByName(GrTypeDefinition grType, String name, boolean checkBases) {
450     if (!checkBases) {
451       for (GrField field : grType.getFields()) {
452         if (name.equals(field.getName())) return field;
453       }
454
455       return null;
456     }
457
458     Map<String, CandidateInfo> fieldsMap = CollectClassMembersUtil.getAllFields(grType);
459     final CandidateInfo info = fieldsMap.get(name);
460     return info == null ? null : (PsiField)info.getElement();
461   }
462
463   public static PsiField[] getAllFields(GrTypeDefinition grType) {
464     Map<String, CandidateInfo> fieldsMap = CollectClassMembersUtil.getAllFields(grType);
465     return ContainerUtil.map2Array(fieldsMap.values(), PsiField.class, new Function<CandidateInfo, PsiField>() {
466       public PsiField fun(CandidateInfo entry) {
467         return (PsiField)entry.getElement();
468       }
469     });
470   }
471
472   public static boolean isClassEquivalentTo(GrTypeDefinitionImpl definition, PsiElement another) {
473     return PsiClassImplUtil.isClassEquivalentTo(definition, another);
474   }
475 }