2 * Copyright 2000-2017 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.compiler.chainsSearch.context;
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;
42 import java.util.List;
44 import java.util.Objects;
46 import java.util.function.Predicate;
47 import java.util.stream.Collectors;
48 import java.util.stream.Stream;
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");
57 private final ChainSearchTarget myTarget;
59 private final List<PsiNamedElement> myContextElements;
61 private final PsiElement myContext;
63 private final GlobalSearchScope myResolveScope;
65 private final Project myProject;
67 private final PsiResolveHelper myResolveHelper;
69 private final TIntObjectHashMap<PsiClass> myQualifierClassResolver;
71 private final Map<MethodCall, PsiMethod[]> myResolver;
73 private final CompilerReferenceServiceEx myRefServiceEx;
75 private final NotNullLazyValue<Set<LightRef>> myContextClassReferences = new NotNullLazyValue<Set<LightRef>>() {
78 protected Set<LightRef> compute() {
79 return getContextTypes()
81 .map(PsiUtil::resolveClassInType)
82 .filter(Objects::nonNull)
83 .map(c -> ClassUtil.getJVMClassName(c))
84 .filter(Objects::nonNull)
85 .mapToInt(c -> myRefServiceEx.getNameId(c))
87 .mapToObj(n -> new LightRef.JavaLightClassRef(n)).collect(Collectors.toSet());
91 public ChainCompletionContext(@NotNull ChainSearchTarget target,
92 @NotNull List<PsiNamedElement> contextElements,
93 @NotNull PsiElement context) {
95 myContextElements = contextElements;
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);
106 public ChainSearchTarget getTarget() {
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)) {
123 public CompilerReferenceServiceEx getRefService() {
124 return myRefServiceEx;
128 public PsiElement getContextPsi() {
132 public PsiFile getContextFile() {
133 return myContext.getContainingFile();
137 public Set<PsiType> getContextTypes() {
138 return myContextElements.stream().map(ChainCompletionContext::getType).collect(Collectors.toSet());
142 public Set<LightRef> getContextClassReferences() {
143 return myContextClassReferences.getValue();
147 public GlobalSearchScope getResolveScope() {
148 return myResolveScope;
152 public Project getProject() {
156 public boolean hasQualifier(@Nullable PsiClass targetClass) {
157 return getQualifiers(targetClass).findAny().isPresent();
160 public Stream<PsiNamedElement> getQualifiers(@Nullable PsiClass targetClass) {
161 if (targetClass == null) return Stream.empty();
162 return getQualifiers(JavaPsiFacade.getInstance(myProject).getElementFactory().createType(targetClass));
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);
173 public PsiClass resolvePsiClass(LightRef.LightClassHierarchyElementDef aClass) {
174 int nameId = aClass.getName();
175 if (myQualifierClassResolver.contains(nameId)) {
176 return myQualifierClassResolver.get(nameId);
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;
184 myQualifierClassResolver.put(nameId, psiClass);
190 public PsiMethod[] resolve(MethodCall sign) {
191 return myResolver.get(sign);
194 public Predicate<PsiMember> accessValidator() {
195 return m -> myResolveHelper.isAccessible(m, myContext, null);
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();
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();
213 return new ChainCompletionContext(target, contextElements, containingElement);
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);
226 parent = parent.getParent();
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;
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);
245 myExcludedVariables = excludedVariables;
249 public boolean shouldProcess(DeclarationKind kind) {
250 return kind == DeclarationKind.ENUM_CONST ||
251 kind == DeclarationKind.FIELD ||
252 kind == DeclarationKind.METHOD ||
253 kind == DeclarationKind.VARIABLE;
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);
265 if (isWidelyUsed(type)) {
268 myContextElements.add((PsiNamedElement)element);
274 public <T> T getHint(@NotNull Key<T> hintKey) {
275 if (hintKey == ElementClassHint.KEY) {
276 //noinspection unchecked
279 return super.getHint(hintKey);
283 public List<PsiNamedElement> getContextElements() {
284 myContextElements.remove(myCompletionVariable);
285 return myContextElements;
290 private static PsiType getType(PsiElement element) {
291 if (element instanceof PsiVariable) {
292 return ((PsiVariable)element).getType();
294 if (element instanceof PsiMethod) {
295 return ((PsiMethod)element).getReturnType();
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)) {