IDEA-52997: Groovy: good code is red. A field and a property of the same name are...
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / resolve / CollectClassMembersUtil.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 package org.jetbrains.plugins.groovy.lang.resolve;
17
18 import com.intellij.openapi.util.Key;
19 import com.intellij.openapi.util.Trinity;
20 import com.intellij.psi.*;
21 import com.intellij.psi.infos.CandidateInfo;
22 import com.intellij.psi.util.*;
23 import com.intellij.util.containers.HashMap;
24 import com.intellij.util.containers.HashSet;
25 import org.jetbrains.annotations.NotNull;
26 import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
27 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
28 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
29
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 /**
36  * @author ven
37  */
38 public class CollectClassMembersUtil {
39   private static final Key<CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>> CACHED_MEMBERS = Key.create("CACHED_CLASS_MEMBERS");
40
41   private static final Key<CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>> CACHED_MEMBERS_INCLUDING_SYNTHETIC = Key.create("CACHED_MEMBERS_INCLUDING_SYNTHETIC");
42
43   private CollectClassMembersUtil() {
44   }
45
46
47   public static Map<String, List<CandidateInfo>> getAllMethods(final PsiClass aClass, boolean includeSynthetic) {
48     return getCachedMembers(aClass, includeSynthetic).getSecond();
49   }
50
51   @NotNull
52   private static Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>> getCachedMembers(
53     PsiClass aClass,
54     boolean includeSynthetic) {
55     Key<CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>> key =
56       includeSynthetic ? CACHED_MEMBERS_INCLUDING_SYNTHETIC : CACHED_MEMBERS;
57     CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>> cachedValue = aClass.getUserData(key);
58     if (cachedValue == null) {
59       cachedValue = buildCache(aClass, includeSynthetic);
60       aClass.putUserData(key, cachedValue);
61     }
62     return cachedValue.getValue();
63   }
64
65   public static Map<String, CandidateInfo> getAllInnerClasses(final PsiClass aClass, boolean includeSynthetic) {
66     return getCachedMembers(aClass, includeSynthetic).getThird();
67   }
68
69   public static Map<String, CandidateInfo> getAllFields(final PsiClass aClass) {
70     return getCachedMembers(aClass, false).getFirst();
71   }
72
73   private static CachedValue<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>> buildCache(final PsiClass aClass, final boolean includeSynthetic) {
74     return CachedValuesManager.getManager(aClass.getProject()).createCachedValue(new CachedValueProvider<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>>() {
75       public Result<Trinity<Map<String, CandidateInfo>, Map<String, List<CandidateInfo>>, Map<String, CandidateInfo>>> compute() {
76         Map<String, CandidateInfo> allFields = new HashMap<String, CandidateInfo>();
77         Map<String, List<CandidateInfo>> allMethods = new HashMap<String, List<CandidateInfo>>();
78         Map<String, CandidateInfo> allInnerClasses = new HashMap<String, CandidateInfo>();
79
80         processClass(aClass, allFields, allMethods, allInnerClasses, new HashSet<PsiClass>(), PsiSubstitutor.EMPTY, includeSynthetic);
81         return Result.create(Trinity.create(allFields, allMethods, allInnerClasses), PsiModificationTracker.OUT_OF_CODE_BLOCK_MODIFICATION_COUNT);
82       }
83     }, false);
84   }
85
86   private static void processClass(PsiClass aClass, Map<String, CandidateInfo> allFields, Map<String, List<CandidateInfo>> allMethods, Map<String, CandidateInfo> allInnerClasses, Set<PsiClass> visitedClasses, PsiSubstitutor substitutor, boolean includeSynthetic) {
87     if (visitedClasses.contains(aClass)) return;
88     visitedClasses.add(aClass);
89
90     for (PsiField field : aClass.getFields()) {
91       String name = field.getName();
92       if (!allFields.containsKey(name)) {
93         allFields.put(name, new CandidateInfo(field, substitutor));
94       } else if (hasExplicitVisibilityModifiers(field)) {
95         final CandidateInfo candidateInfo = allFields.get(name);
96         final PsiElement element = candidateInfo.getElement();
97         if (element instanceof GrField &&
98             !(((GrField)element).getModifierList()).hasExplicitVisibilityModifiers() &&
99             aClass == ((GrField)element).getContainingClass()) { //replace property-field with field with explicit visibilityModifier 
100           allFields.put(name, new CandidateInfo(field, substitutor));
101         }
102       }
103     }
104
105     for (PsiMethod method : includeSynthetic || !(aClass instanceof GrTypeDefinition) ? aClass.getMethods() : ((GrTypeDefinition) aClass).getGroovyMethods()) {
106       addMethod(allMethods, method, substitutor);
107     }
108
109     for (final PsiClass inner : aClass.getInnerClasses()) {
110       final String name = inner.getName();
111       if (name != null && !allInnerClasses.containsKey(name)) {
112         allInnerClasses.put(name, new CandidateInfo(inner, substitutor));
113       }
114     }
115
116     for (PsiClassType superType : aClass.getSuperTypes()) {
117       PsiClass superClass = superType.resolve();
118       if (superClass != null) {
119         final PsiSubstitutor superSubstitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, aClass, substitutor);
120         processClass(superClass, allFields, allMethods, allInnerClasses, visitedClasses, superSubstitutor, includeSynthetic);
121       }
122     }
123   }
124
125   private static boolean hasExplicitVisibilityModifiers(PsiField field) {
126     if (field instanceof GrField) {
127       return ((GrModifierList)field.getModifierList()).hasExplicitVisibilityModifiers();
128     }
129     else {
130       return true;
131     }
132   }
133
134   private static void addMethod(Map<String, List<CandidateInfo>> allMethods, PsiMethod method, PsiSubstitutor substitutor) {
135     String name = method.getName();
136     List<CandidateInfo> methods = allMethods.get(name);
137     if (methods == null) {
138       methods = new ArrayList<CandidateInfo>();
139       allMethods.put(name, methods);
140       methods.add(new CandidateInfo(method, substitutor));
141     } else methods.add(new CandidateInfo(method, substitutor));
142   }
143 }