Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / JavaNoVariantsDelegator.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.codeInsight.completion;
17
18 import com.intellij.codeInsight.ExpectedTypeInfo;
19 import com.intellij.codeInsight.completion.impl.BetterPrefixMatcher;
20 import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
21 import com.intellij.codeInsight.lookup.AutoCompletionPolicy;
22 import com.intellij.codeInsight.lookup.LookupElement;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.psi.*;
25 import com.intellij.psi.filters.ElementFilter;
26 import com.intellij.psi.search.PsiShortNamesCache;
27 import com.intellij.util.CollectConsumer;
28 import com.intellij.util.Consumer;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import java.util.Collections;
33 import java.util.LinkedHashSet;
34 import java.util.Set;
35
36 import static com.intellij.patterns.PsiJavaPatterns.psiElement;
37
38 /**
39  * @author peter
40  */
41 public class JavaNoVariantsDelegator extends CompletionContributor {
42   @Override
43   public void fillCompletionVariants(@NotNull final CompletionParameters parameters, @NotNull final CompletionResultSet result) {
44     ResultTracker tracker = new ResultTracker(result) {
45       @Override
46       public void consume(CompletionResult plainResult) {
47         super.consume(plainResult);
48
49         LookupElement element = plainResult.getLookupElement();
50         if (element instanceof TypeArgumentCompletionProvider.TypeArgsLookupElement) {
51           ((TypeArgumentCompletionProvider.TypeArgsLookupElement)element).registerSingleClass(session);
52         }
53       }
54     };
55     result.runRemainingContributors(parameters, tracker);
56     final boolean empty = tracker.containsOnlyPackages || suggestAllAnnotations(parameters);
57
58     if (JavaCompletionContributor.isClassNamePossible(parameters) && !JavaCompletionContributor.mayStartClassName(result)) {
59       result.restartCompletionOnAnyPrefixChange();
60     }
61
62     if (empty) {
63       delegate(parameters, JavaCompletionSorting.addJavaSorting(parameters, result), tracker.session);
64     } else {
65       if (parameters.getCompletionType() == CompletionType.BASIC &&
66           parameters.getInvocationCount() <= 1 &&
67           JavaCompletionContributor.mayStartClassName(result) &&
68           JavaCompletionContributor.isClassNamePossible(parameters)) {
69         suggestNonImportedClasses(parameters, JavaCompletionSorting.addJavaSorting(parameters, result.withPrefixMatcher(tracker.betterMatcher)), tracker.session);
70       }
71     }
72   }
73
74   private static boolean suggestAllAnnotations(CompletionParameters parameters) {
75     return psiElement().withParents(PsiJavaCodeReferenceElement.class, PsiAnnotation.class).accepts(parameters.getPosition());
76   }
77
78   private static void delegate(CompletionParameters parameters, CompletionResultSet result, JavaCompletionSession session) {
79     if (parameters.getCompletionType() == CompletionType.BASIC) {
80       PsiElement position = parameters.getPosition();
81       suggestCollectionUtilities(parameters, result, position);
82
83       if (parameters.getInvocationCount() <= 1 &&
84           (JavaCompletionContributor.mayStartClassName(result) || suggestAllAnnotations(parameters)) &&
85           JavaCompletionContributor.isClassNamePossible(parameters)) {
86         suggestNonImportedClasses(parameters, result, session);
87         return;
88       }
89
90       suggestChainedCalls(parameters, result, position);
91     }
92
93     if (parameters.getCompletionType() == CompletionType.SMART && parameters.getInvocationCount() == 2) {
94       result.runRemainingContributors(parameters.withInvocationCount(3), true);
95     }
96   }
97
98   private static void suggestCollectionUtilities(CompletionParameters parameters, final CompletionResultSet result, PsiElement position) {
99     if (StringUtil.isNotEmpty(result.getPrefixMatcher().getPrefix())) {
100       for (ExpectedTypeInfo info : JavaSmartCompletionContributor.getExpectedTypes(parameters)) {
101         new CollectionsUtilityMethodsProvider(position, info.getType(), info.getDefaultType(), result).addCompletions(true);
102       }
103     }
104   }
105
106   private static void suggestChainedCalls(CompletionParameters parameters, CompletionResultSet result, PsiElement position) {
107     PsiElement parent = position.getParent();
108     if (!(parent instanceof PsiJavaCodeReferenceElement) || parent.getParent() instanceof PsiImportStatementBase) {
109       return;
110     }
111     PsiElement qualifier = ((PsiJavaCodeReferenceElement)parent).getQualifier();
112     if (!(qualifier instanceof PsiJavaCodeReferenceElement) ||
113         ((PsiJavaCodeReferenceElement)qualifier).isQualified()) {
114       return;
115     }
116     PsiElement target = ((PsiJavaCodeReferenceElement)qualifier).resolve();
117     if (target != null && !(target instanceof PsiPackage)) {
118       return;
119     }
120
121     PsiFile file = position.getContainingFile();
122     if (file instanceof PsiJavaCodeReferenceCodeFragment) {
123       return;
124     }
125
126     String fullPrefix = parent.getText().substring(0, parameters.getOffset() - parent.getTextRange().getStartOffset());
127     CompletionResultSet qualifiedCollector = result.withPrefixMatcher(fullPrefix);
128     ElementFilter filter = JavaCompletionContributor.getReferenceFilter(position);
129     for (LookupElement base : suggestQualifierItems(parameters, (PsiJavaCodeReferenceElement)qualifier, filter)) {
130       PsiType type = JavaCompletionUtil.getLookupElementType(base);
131       if (type != null && !PsiType.VOID.equals(type)) {
132         String separator = parent instanceof PsiMethodReferenceExpression ? "::" : ".";
133         PsiReferenceExpression ref = ReferenceExpressionCompletionContributor.createMockReference(position, type, base, separator);
134         if (ref != null) {
135           for (LookupElement item : JavaSmartCompletionContributor.completeReference(position, ref, filter, true, true, parameters,
136                                                                                      result.getPrefixMatcher())) {
137             qualifiedCollector.addElement(JavaCompletionUtil.highlightIfNeeded(null, new JavaChainLookupElement(base, item, separator), item.getObject(), position));
138           }
139         }
140       }
141     }
142   }
143
144   private static Set<LookupElement> suggestQualifierItems(CompletionParameters parameters,
145                                                           PsiJavaCodeReferenceElement qualifier,
146                                                           ElementFilter filter) {
147     String referenceName = qualifier.getReferenceName();
148     if (referenceName == null) {
149       return Collections.emptySet();
150     }
151
152     PrefixMatcher qMatcher = new CamelHumpMatcher(referenceName);
153     Set<LookupElement> plainVariants =
154       JavaSmartCompletionContributor.completeReference(qualifier, qualifier, filter, true, true, parameters, qMatcher);
155
156     for (PsiClass aClass : PsiShortNamesCache.getInstance(qualifier.getProject()).getClassesByName(referenceName, qualifier.getResolveScope())) {
157       plainVariants.add(JavaClassNameCompletionContributor.createClassLookupItem(aClass, true));
158     }
159
160     if (!plainVariants.isEmpty()) {
161       return plainVariants;
162     }
163
164     final Set<LookupElement> allClasses = new LinkedHashSet<>();
165     PsiElement qualifierName = qualifier.getReferenceNameElement();
166     if (qualifierName != null) {
167       JavaClassNameCompletionContributor.addAllClasses(parameters.withPosition(qualifierName, qualifierName.getTextRange().getEndOffset()),
168                                                        true, qMatcher, new CollectConsumer<>(allClasses));
169     }
170     return allClasses;
171   }
172
173   private static void suggestNonImportedClasses(CompletionParameters parameters, CompletionResultSet result, @Nullable JavaCompletionSession session) {
174     JavaClassNameCompletionContributor.addAllClasses(parameters, true, result.getPrefixMatcher(), element -> {
175       if (session != null && session.alreadyProcessed(element)) {
176         return;
177       }
178       JavaPsiClassReferenceElement classElement = element.as(JavaPsiClassReferenceElement.CLASS_CONDITION_KEY);
179       if (classElement != null) {
180         classElement.setAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE);
181       }
182
183       result.addElement(element);
184     });
185   }
186
187   public static class ResultTracker implements Consumer<CompletionResult> {
188     private final CompletionResultSet myResult;
189     public final JavaCompletionSession session;
190     public boolean containsOnlyPackages = true;
191     public BetterPrefixMatcher betterMatcher;
192
193     public ResultTracker(CompletionResultSet result) {
194       myResult = result;
195       betterMatcher = new BetterPrefixMatcher.AutoRestarting(result);
196       session = new JavaCompletionSession(result);
197     }
198
199     @Override
200     public void consume(CompletionResult plainResult) {
201       myResult.passResult(plainResult);
202
203       LookupElement element = plainResult.getLookupElement();
204       if (containsOnlyPackages && !(CompletionUtil.getTargetElement(element) instanceof PsiPackage)) {
205         containsOnlyPackages = false;
206       }
207
208       session.registerClassFrom(element);
209
210       betterMatcher = betterMatcher.improve(plainResult);
211     }
212   }
213 }