type casts in relevant method chain completion: various renames
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / chainsSearch / context / ChainCompletionContext.java
1 /*
2  * Copyright 2000-2017 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 com.intellij.compiler.chainsSearch.context;
17
18 import com.intellij.compiler.CompilerReferenceService;
19 import com.intellij.compiler.backwardRefs.CompilerReferenceServiceEx;
20 import com.intellij.compiler.chainsSearch.MethodCall;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.util.Key;
23 import com.intellij.openapi.util.NotNullLazyValue;
24 import com.intellij.psi.*;
25 import com.intellij.psi.scope.BaseScopeProcessor;
26 import com.intellij.psi.scope.ElementClassHint;
27 import com.intellij.psi.scope.util.PsiScopesUtil;
28 import com.intellij.psi.search.GlobalSearchScope;
29 import com.intellij.psi.util.ClassUtil;
30 import com.intellij.psi.util.PropertyUtil;
31 import com.intellij.psi.util.PsiTreeUtil;
32 import com.intellij.psi.util.PsiUtil;
33 import com.intellij.util.SmartList;
34 import com.intellij.util.containers.ContainerUtil;
35 import com.intellij.util.containers.FactoryMap;
36 import gnu.trove.THashSet;
37 import gnu.trove.TIntObjectHashMap;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.jetbrains.jps.backwardRefs.LightRef;
41
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Objects;
45 import java.util.Set;
46 import java.util.function.Predicate;
47 import java.util.stream.Collectors;
48 import java.util.stream.Stream;
49
50 public class ChainCompletionContext {
51   private static final String[] WIDELY_USED_CLASS_NAMES = new String [] {CommonClassNames.JAVA_LANG_STRING,
52                                                                   CommonClassNames.JAVA_LANG_OBJECT,
53                                                                   CommonClassNames.JAVA_LANG_CLASS};
54   private static final Set<String> WIDELY_USED_SHORT_NAMES = ContainerUtil.set("String", "Object", "Class");
55
56   @NotNull
57   private final ChainSearchTarget myTarget;
58   @NotNull
59   private final List<PsiNamedElement> myContextElements;
60   @NotNull
61   private final PsiElement myContext;
62   @NotNull
63   private final GlobalSearchScope myResolveScope;
64   @NotNull
65   private final Project myProject;
66   @NotNull
67   private final PsiResolveHelper myResolveHelper;
68   @NotNull
69   private final TIntObjectHashMap<PsiClass> myQualifierClassResolver;
70   @NotNull
71   private final Map<MethodCall, PsiMethod[]> myResolver;
72   @NotNull
73   private final CompilerReferenceServiceEx myRefServiceEx;
74
75   private final NotNullLazyValue<Set<LightRef>> myContextClassReferences = new NotNullLazyValue<Set<LightRef>>() {
76     @NotNull
77     @Override
78     protected Set<LightRef> compute() {
79       return getContextTypes()
80         .stream()
81         .map(PsiUtil::resolveClassInType)
82         .filter(Objects::nonNull)
83         .map(c -> ClassUtil.getJVMClassName(c))
84         .filter(Objects::nonNull)
85         .mapToInt(c -> myRefServiceEx.getNameId(c))
86         .filter(n -> n != 0)
87         .mapToObj(n -> new LightRef.JavaLightClassRef(n)).collect(Collectors.toSet());
88     }
89   };
90
91   public ChainCompletionContext(@NotNull ChainSearchTarget target,
92                                 @NotNull List<PsiNamedElement> contextElements,
93                                 @NotNull PsiElement context) {
94     myTarget = target;
95     myContextElements = contextElements;
96     myContext = context;
97     myResolveScope = context.getResolveScope();
98     myProject = context.getProject();
99     myResolveHelper = PsiResolveHelper.SERVICE.getInstance(myProject);
100     myQualifierClassResolver = new TIntObjectHashMap<>();
101     myResolver = FactoryMap.createMap(sign -> sign.resolve());
102     myRefServiceEx = (CompilerReferenceServiceEx)CompilerReferenceService.getInstance(myProject);
103   }
104
105   @NotNull
106   public ChainSearchTarget getTarget() {
107     return myTarget;
108   }
109
110   public boolean contains(@Nullable PsiType type) {
111     if (type == null) return false;
112     Set<PsiType> types = getContextTypes();
113     if (types.contains(type)) return true;
114     for (PsiType contextType : types) {
115       if (type.isAssignableFrom(contextType)) {
116         return true;
117       }
118     }
119     return false;
120   }
121
122   @NotNull
123   public CompilerReferenceServiceEx getRefService() {
124     return myRefServiceEx;
125   }
126
127   @NotNull
128   public PsiElement getContextPsi() {
129     return myContext;
130   }
131
132   public PsiFile getContextFile() {
133     return myContext.getContainingFile();
134   }
135
136   @NotNull
137   public Set<PsiType> getContextTypes() {
138     return myContextElements.stream().map(ChainCompletionContext::getType).collect(Collectors.toSet());
139   }
140
141   @NotNull
142   public Set<LightRef> getContextClassReferences() {
143     return myContextClassReferences.getValue();
144   }
145
146   @NotNull
147   public GlobalSearchScope getResolveScope() {
148     return myResolveScope;
149   }
150
151   @NotNull
152   public Project getProject() {
153     return myProject;
154   }
155
156   public boolean hasQualifier(@Nullable PsiClass targetClass) {
157     return getQualifiers(targetClass).findAny().isPresent();
158   }
159
160   public Stream<PsiNamedElement> getQualifiers(@Nullable PsiClass targetClass) {
161     if (targetClass == null) return Stream.empty();
162     return getQualifiers(JavaPsiFacade.getInstance(myProject).getElementFactory().createType(targetClass));
163   }
164
165   public Stream<PsiNamedElement> getQualifiers(@NotNull PsiType targetType) {
166     return myContextElements.stream().filter(e -> {
167       PsiType elementType = getType(e);
168       return elementType != null && targetType.isAssignableFrom(elementType);
169     });
170   }
171
172   @Nullable
173   public PsiClass resolvePsiClass(LightRef.LightClassHierarchyElementDef aClass) {
174     int nameId = aClass.getName();
175     if (myQualifierClassResolver.contains(nameId)) {
176       return myQualifierClassResolver.get(nameId);
177     } else {
178       PsiClass psiClass = null;
179       String name = myRefServiceEx.getName(nameId);
180       PsiClass resolvedClass = JavaPsiFacade.getInstance(getProject()).findClass(name, myResolveScope);
181       if (resolvedClass != null && accessValidator().test(resolvedClass)) {
182         psiClass = resolvedClass;
183       }
184       myQualifierClassResolver.put(nameId, psiClass);
185       return psiClass;
186     }
187   }
188
189   @NotNull
190   public PsiMethod[] resolve(MethodCall sign) {
191     return myResolver.get(sign);
192   }
193
194   public Predicate<PsiMember> accessValidator() {
195     return m -> myResolveHelper.isAccessible(m, myContext, null);
196   }
197
198   @Nullable
199   public static ChainCompletionContext createContext(@Nullable PsiType targetType,
200                                                      @Nullable PsiElement containingElement, boolean suggestIterators) {
201     if (containingElement == null) return null;
202     ChainSearchTarget target = ChainSearchTarget.create(targetType);
203     if (target == null) return null;
204     if (suggestIterators) {
205       target = target.toIterators();
206     }
207
208     Set<? extends PsiVariable> excludedVariables = getEnclosingLocalVariables(containingElement);
209     ContextProcessor processor = new ContextProcessor(null, containingElement.getProject(), containingElement, excludedVariables);
210     PsiScopesUtil.treeWalkUp(processor, containingElement, containingElement.getContainingFile());
211     List<PsiNamedElement> contextElements = processor.getContextElements();
212
213     return new ChainCompletionContext(target, contextElements, containingElement);
214   }
215
216   @NotNull
217   private static Set<? extends PsiVariable> getEnclosingLocalVariables(@NotNull PsiElement place) {
218     Set<PsiLocalVariable> result = new THashSet<>();
219     if (place instanceof PsiLocalVariable) result.add((PsiLocalVariable)place);
220     PsiElement parent = place.getParent();
221     while (parent != null) {
222       if (parent instanceof PsiFileSystemItem) break;
223       if (parent instanceof PsiLocalVariable && PsiTreeUtil.isAncestor(((PsiLocalVariable)parent).getInitializer(), place, false)) {
224         result.add((PsiLocalVariable)parent);
225       }
226       parent = parent.getParent();
227     }
228     return result;
229   }
230
231   private static class ContextProcessor extends BaseScopeProcessor implements ElementClassHint {
232     private final List<PsiNamedElement> myContextElements = new SmartList<>();
233     private final PsiVariable myCompletionVariable;
234     private final PsiResolveHelper myResolveHelper;
235     private final PsiElement myPlace;
236     private final Set<? extends PsiVariable> myExcludedVariables;
237
238     private ContextProcessor(@Nullable PsiVariable variable,
239                              @NotNull Project project,
240                              @NotNull PsiElement place,
241                              @NotNull Set<? extends PsiVariable> excludedVariables) {
242       myCompletionVariable = variable;
243       myResolveHelper = PsiResolveHelper.SERVICE.getInstance(project);
244       myPlace = place;
245       myExcludedVariables = excludedVariables;
246     }
247
248     @Override
249     public boolean shouldProcess(DeclarationKind kind) {
250       return kind == DeclarationKind.ENUM_CONST ||
251              kind == DeclarationKind.FIELD ||
252              kind == DeclarationKind.METHOD ||
253              kind == DeclarationKind.VARIABLE;
254     }
255
256     @Override
257     public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
258       if ((!(element instanceof PsiMethod) || PropertyUtil.isSimplePropertyAccessor((PsiMethod)element)) &&
259           (!(element instanceof PsiVariable) || !myExcludedVariables.contains(element)) &&
260           (!(element instanceof PsiMember) || myResolveHelper.isAccessible((PsiMember)element, myPlace, null))) {
261         PsiType type = getType(element);
262         if (type == null) {
263           return false;
264         }
265         if (isWidelyUsed(type)) {
266           return false;
267         }
268         myContextElements.add((PsiNamedElement)element);
269       }
270       return true;
271     }
272
273     @Override
274     public <T> T getHint(@NotNull Key<T> hintKey) {
275       if (hintKey == ElementClassHint.KEY) {
276         //noinspection unchecked
277         return (T)this;
278       }
279       return super.getHint(hintKey);
280     }
281
282     @NotNull
283     public List<PsiNamedElement> getContextElements() {
284       myContextElements.remove(myCompletionVariable);
285       return myContextElements;
286     }
287   }
288
289   @Nullable
290   private static PsiType getType(PsiElement element) {
291     if (element instanceof PsiVariable) {
292       return ((PsiVariable)element).getType();
293     }
294     if (element instanceof PsiMethod) {
295       return ((PsiMethod)element).getReturnType();
296     }
297     return null;
298   }
299
300   public static boolean isWidelyUsed(@NotNull PsiType type) {
301     type = type.getDeepComponentType();
302     if (type instanceof PsiPrimitiveType) return true;
303     if (!(type instanceof PsiClassType)) return false;
304     if (WIDELY_USED_SHORT_NAMES.contains(((PsiClassType)type).getClassName())) return false;
305     final PsiClass resolvedClass = ((PsiClassType)type).resolve();
306     if (resolvedClass == null) return false;
307     final String qName = resolvedClass.getQualifiedName();
308     if (qName == null) return false;
309     for (String name : WIDELY_USED_CLASS_NAMES) {
310       if (name.equals(qName)) {
311         return true;
312       }
313     }
314     return false;
315   }
316 }