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