IDEA-147454 Smart completion add parentheses to class declaration in throws statement
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / JavaSmartCompletionContributor.java
1 /*
2  * Copyright 2000-2014 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.ExpectedTypeInfoImpl;
20 import com.intellij.codeInsight.ExpectedTypesProvider;
21 import com.intellij.codeInsight.TailType;
22 import com.intellij.codeInsight.completion.scope.JavaCompletionProcessor;
23 import com.intellij.codeInsight.lookup.LookupElement;
24 import com.intellij.codeInsight.lookup.LookupElementDecorator;
25 import com.intellij.patterns.ElementPattern;
26 import com.intellij.psi.*;
27 import com.intellij.psi.filters.ElementExtractorFilter;
28 import com.intellij.psi.filters.ElementFilter;
29 import com.intellij.psi.filters.OrFilter;
30 import com.intellij.psi.filters.getters.ExpectedTypesGetter;
31 import com.intellij.psi.filters.getters.JavaMembersGetter;
32 import com.intellij.psi.filters.types.AssignableFromFilter;
33 import com.intellij.psi.filters.types.AssignableToFilter;
34 import com.intellij.psi.infos.CandidateInfo;
35 import com.intellij.psi.util.PsiTreeUtil;
36 import com.intellij.util.*;
37 import com.intellij.util.containers.ContainerUtil;
38 import gnu.trove.THashSet;
39 import gnu.trove.TObjectHashingStrategy;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42
43 import java.util.*;
44
45 import static com.intellij.patterns.PlatformPatterns.psiElement;
46 import static com.intellij.patterns.StandardPatterns.or;
47
48 /**
49  * @author peter
50  */
51 public class JavaSmartCompletionContributor extends CompletionContributor {
52   private static final TObjectHashingStrategy<ExpectedTypeInfo> EXPECTED_TYPE_INFO_STRATEGY = new TObjectHashingStrategy<ExpectedTypeInfo>() {
53     @Override
54     public int computeHashCode(final ExpectedTypeInfo object) {
55       return object.getType().hashCode();
56     }
57
58     @Override
59     public boolean equals(final ExpectedTypeInfo o1, final ExpectedTypeInfo o2) {
60       return o1.getType().equals(o2.getType());
61     }
62   };
63
64   private static final ElementExtractorFilter THROWABLES_FILTER = new ElementExtractorFilter(new AssignableFromFilter(CommonClassNames.JAVA_LANG_THROWABLE));
65   public static final ElementPattern<PsiElement> AFTER_NEW =
66       psiElement().afterLeaf(
67           psiElement().withText(PsiKeyword.NEW).andNot(
68               psiElement().afterLeaf(
69                   psiElement().withText(PsiKeyword.THROW))));
70   static final ElementPattern<PsiElement> AFTER_THROW_NEW = psiElement().afterLeaf(psiElement().withText(PsiKeyword.NEW).afterLeaf(PsiKeyword.THROW));
71   public static final ElementPattern<PsiElement> INSIDE_EXPRESSION = or(
72         psiElement().withParent(PsiExpression.class).andNot(psiElement().withParent(PsiLiteralExpression.class)).andNot(psiElement().withParent(PsiMethodReferenceExpression.class)),
73         psiElement().inside(PsiClassObjectAccessExpression.class),
74         psiElement().inside(PsiThisExpression.class),
75         psiElement().inside(PsiSuperExpression.class)
76         );
77   static final ElementPattern<PsiElement> INSIDE_TYPECAST_EXPRESSION = psiElement().withParent(
78     psiElement(PsiReferenceExpression.class).afterLeaf(
79       psiElement().withText(")").withParent(PsiTypeCastExpression.class)));
80
81   @Nullable
82   private static ElementFilter getClassReferenceFilter(PsiElement element) {
83     //throw new foo
84     if (AFTER_THROW_NEW.accepts(element)) {
85       return THROWABLES_FILTER;
86     }
87     
88     //new xxx.yyy
89     if (psiElement().afterLeaf(psiElement().withText(".")).withSuperParent(2, psiElement(PsiNewExpression.class)).accepts(element)) {
90       if (((PsiNewExpression)element.getParent().getParent()).getClassReference() == element.getParent()) {
91         PsiType[] types = ExpectedTypesGetter.getExpectedTypes(element, false);
92         return new OrFilter(ContainerUtil.map2Array(types, ElementFilter.class, new Function<PsiType, ElementFilter>() {
93           @Override
94           public ElementFilter fun(PsiType type) {
95             return new AssignableFromFilter(type);
96           }
97         }));
98       }
99     }
100
101     return null;
102   }
103
104   private static boolean isInsideThrowsList(PsiElement element) {
105     PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
106     return method != null && PsiTreeUtil.isAncestor(method.getThrowsList(), element, true);
107   }
108
109
110   public JavaSmartCompletionContributor() {
111     extend(CompletionType.SMART, SmartCastProvider.TYPECAST_TYPE_CANDIDATE, new SmartCastProvider());
112
113     extend(CompletionType.SMART, SameSignatureCallParametersProvider.IN_CALL_ARGUMENT, new SameSignatureCallParametersProvider());
114     
115     extend(CompletionType.SMART, MethodReturnTypeProvider.IN_METHOD_RETURN_TYPE, new MethodReturnTypeProvider());
116
117     extend(CompletionType.SMART, InstanceofTypeProvider.AFTER_INSTANCEOF, new InstanceofTypeProvider());
118
119     extend(CompletionType.SMART, psiElement(), new CompletionProvider<CompletionParameters>() {
120       @Override
121       protected void addCompletions(@NotNull final CompletionParameters parameters, final ProcessingContext context, @NotNull final CompletionResultSet result) {
122         if (SmartCastProvider.shouldSuggestCast(parameters)) return;
123         
124         final PsiElement element = parameters.getPosition();
125         final PsiJavaCodeReferenceElement reference = 
126           PsiTreeUtil.findElementOfClassAtOffset(element.getContainingFile(), parameters.getOffset(), PsiJavaCodeReferenceElement.class, false);
127         if (reference != null) {
128           ElementFilter filter = getClassReferenceFilter(element);
129           boolean completeConstructor = filter != null;
130           if (filter == null && isInsideThrowsList(element)) {
131             filter = THROWABLES_FILTER;
132           }
133           if (filter != null) {
134             final List<ExpectedTypeInfo> infos = Arrays.asList(getExpectedTypes(parameters));
135             for (LookupElement item : completeReference(element, reference, filter, true, false, parameters, result.getPrefixMatcher())) {
136               if (item.getObject() instanceof PsiClass) {
137                 if (completeConstructor) {
138                   item = LookupElementDecorator.withInsertHandler(item, ConstructorInsertHandler.SMART_INSTANCE);
139                 }
140                 result.addElement(decorate(item, infos));
141               }
142             }
143           }
144           else if (INSIDE_TYPECAST_EXPRESSION.accepts(element)) {
145             final PsiTypeCastExpression cast = PsiTreeUtil.getContextOfType(element, PsiTypeCastExpression.class, true);
146             if (cast != null && cast.getCastType() != null) {
147               filter = new AssignableToFilter(cast.getCastType().getType());
148               for (final LookupElement item : completeReference(element, reference, filter, false, true, parameters, result.getPrefixMatcher())) {
149                 result.addElement(item);
150               }
151             }
152           }
153
154         }
155       }
156     });
157
158     extend(CompletionType.SMART, INSIDE_EXPRESSION, new ExpectedTypeBasedCompletionProvider() {
159       @Override
160       protected void addCompletions(final CompletionParameters params, final CompletionResultSet result, final Collection<ExpectedTypeInfo> _infos) {
161         if (SmartCastProvider.shouldSuggestCast(params)) return;
162
163         Consumer<LookupElement> noTypeCheck = decorateWithoutTypeCheck(result, _infos);
164
165         THashSet<ExpectedTypeInfo> mergedInfos = new THashSet<ExpectedTypeInfo>(_infos, EXPECTED_TYPE_INFO_STRATEGY);
166         List<Runnable> chainedEtc = new ArrayList<Runnable>();
167         for (final ExpectedTypeInfo info : mergedInfos) {
168           Runnable slowContinuation =
169             ReferenceExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(params, info), noTypeCheck);
170           ContainerUtil.addIfNotNull(chainedEtc, slowContinuation);
171         }
172         addExpectedTypeMembers(params, mergedInfos, true, noTypeCheck);
173
174         PsiElement parent = params.getPosition().getParent();
175         if (parent instanceof PsiReferenceExpression) {
176           CollectConversion.addCollectConversion((PsiReferenceExpression)parent, mergedInfos, noTypeCheck);
177         }
178
179         for (final ExpectedTypeInfo info : mergedInfos) {
180           BasicExpressionCompletionContributor.fillCompletionVariants(new JavaSmartCompletionParameters(params, info), new Consumer<LookupElement>() {
181             @Override
182             public void consume(LookupElement lookupElement) {
183               final PsiType psiType = JavaCompletionUtil.getLookupElementType(lookupElement);
184               if (psiType != null && info.getType().isAssignableFrom(psiType)) {
185                 result.addElement(decorate(lookupElement, _infos));
186               }
187             }
188           }, result.getPrefixMatcher());
189           
190         }
191
192         for (Runnable runnable : chainedEtc) {
193           runnable.run();
194         }
195
196
197         final boolean searchInheritors = params.getInvocationCount() > 1;
198         if (searchInheritors) {
199           addExpectedTypeMembers(params, mergedInfos, false, noTypeCheck);
200         }
201       }
202     });
203
204     extend(CompletionType.SMART, ExpectedAnnotationsProvider.ANNOTATION_ATTRIBUTE_VALUE, new ExpectedAnnotationsProvider());
205
206     extend(CompletionType.SMART, CatchTypeProvider.CATCH_CLAUSE_TYPE, new CatchTypeProvider());
207
208     extend(CompletionType.SMART, TypeArgumentCompletionProvider.IN_TYPE_ARGS, new TypeArgumentCompletionProvider(true, null));
209
210     extend(CompletionType.SMART, AFTER_NEW, new JavaInheritorsGetter(ConstructorInsertHandler.SMART_INSTANCE));
211
212     extend(CompletionType.SMART, LabelReferenceCompletion.LABEL_REFERENCE, new LabelReferenceCompletion());
213
214     extend(CompletionType.SMART, psiElement(), new FunctionalExpressionCompletionProvider());
215     extend(CompletionType.SMART, psiElement(), new MethodReferenceCompletionProvider());
216   }
217
218   @NotNull
219   static Consumer<LookupElement> decorateWithoutTypeCheck(final CompletionResultSet result, final Collection<ExpectedTypeInfo> infos) {
220     return new Consumer<LookupElement>() {
221       @Override
222       public void consume(final LookupElement lookupElement) {
223         result.addElement(decorate(lookupElement, infos));
224       }
225     };
226   }
227
228   private static void addExpectedTypeMembers(CompletionParameters params,
229                                              THashSet<ExpectedTypeInfo> mergedInfos,
230                                              boolean quick,
231                                              Consumer<LookupElement> consumer) {
232     PsiElement position = params.getPosition();
233     if (!JavaKeywordCompletion.AFTER_DOT.accepts(position)) {
234       for (ExpectedTypeInfo info : mergedInfos) {
235         new JavaMembersGetter(info.getType(), params).addMembers(!quick, consumer);
236         if (!info.getDefaultType().equals(info.getType())) {
237           new JavaMembersGetter(info.getDefaultType(), params).addMembers(!quick, consumer);
238         }
239       }
240     }
241   }
242
243   @Override
244   public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
245     super.fillCompletionVariants(parameters, JavaCompletionSorting.addJavaSorting(parameters, result));
246   }
247
248   public static SmartCompletionDecorator decorate(LookupElement lookupElement, Collection<ExpectedTypeInfo> infos) {
249     return new SmartCompletionDecorator(lookupElement, infos);
250   }
251
252   @NotNull
253   public static ExpectedTypeInfo[] getExpectedTypes(final CompletionParameters parameters) {
254     return getExpectedTypes(parameters.getPosition(), parameters.getCompletionType() == CompletionType.SMART);
255   }
256
257   @NotNull
258   public static ExpectedTypeInfo[] getExpectedTypes(PsiElement position, boolean voidable) {
259     if (psiElement().withParent(psiElement(PsiReferenceExpression.class).withParent(PsiThrowStatement.class)).accepts(position)) {
260       final PsiElementFactory factory = JavaPsiFacade.getInstance(position.getProject()).getElementFactory();
261       final PsiClassType classType = factory
262           .createTypeByFQClassName(CommonClassNames.JAVA_LANG_RUNTIME_EXCEPTION, position.getResolveScope());
263       final List<ExpectedTypeInfo> result = new SmartList<ExpectedTypeInfo>();
264       result.add(new ExpectedTypeInfoImpl(classType, ExpectedTypeInfo.TYPE_OR_SUBTYPE, classType, TailType.SEMICOLON, null, ExpectedTypeInfoImpl.NULL));
265       final PsiMethod method = PsiTreeUtil.getContextOfType(position, PsiMethod.class, true);
266       if (method != null) {
267         for (final PsiClassType type : method.getThrowsList().getReferencedTypes()) {
268           result.add(new ExpectedTypeInfoImpl(type, ExpectedTypeInfo.TYPE_OR_SUBTYPE, type, TailType.SEMICOLON, null, ExpectedTypeInfoImpl.NULL));
269         }
270       }
271       return result.toArray(new ExpectedTypeInfo[result.size()]);
272     }
273
274     PsiExpression expression = PsiTreeUtil.getContextOfType(position, PsiExpression.class, true);
275     if (expression == null) return ExpectedTypeInfo.EMPTY_ARRAY;
276
277     return ExpectedTypesProvider.getExpectedTypes(expression, true, voidable, false);
278   }
279
280   static Set<LookupElement> completeReference(final PsiElement element,
281                                               PsiJavaCodeReferenceElement reference,
282                                               final ElementFilter filter,
283                                               final boolean acceptClasses,
284                                               final boolean acceptMembers,
285                                               CompletionParameters parameters, final PrefixMatcher matcher) {
286     ElementFilter checkClass = new ElementFilter() {
287       @Override
288       public boolean isAcceptable(Object element, PsiElement context) {
289         return filter.isAcceptable(element, context);
290       }
291
292       @Override
293       public boolean isClassAcceptable(Class hintClass) {
294         if (ReflectionUtil.isAssignable(PsiClass.class, hintClass)) {
295           return acceptClasses;
296         }
297
298         if (ReflectionUtil.isAssignable(PsiVariable.class, hintClass) ||
299             ReflectionUtil.isAssignable(PsiMethod.class, hintClass) ||
300             ReflectionUtil.isAssignable(CandidateInfo.class, hintClass)) {
301           return acceptMembers;
302         }
303         return false;
304       }
305     };
306     JavaCompletionProcessor.Options options =
307       JavaCompletionProcessor.Options.DEFAULT_OPTIONS.withFilterStaticAfterInstance(parameters.getInvocationCount() <= 1);
308     return JavaCompletionUtil.processJavaReference(element, reference, checkClass, options, matcher, parameters);
309   }
310
311   @Override
312   public void beforeCompletion(@NotNull CompletionInitializationContext context) {
313     if (context.getCompletionType() != CompletionType.SMART) {
314       return;
315     }
316
317     if (!context.getEditor().getSelectionModel().hasSelection()) {
318       final PsiFile file = context.getFile();
319       PsiElement element = file.findElementAt(context.getStartOffset());
320       if (element instanceof PsiIdentifier) {
321         element = element.getParent();
322         while (element instanceof PsiJavaCodeReferenceElement || element instanceof PsiCall ||
323                element instanceof PsiThisExpression || element instanceof PsiSuperExpression ||
324                element instanceof PsiTypeElement ||
325                element instanceof PsiClassObjectAccessExpression) {
326           int newEnd = element.getTextRange().getEndOffset();
327           if (element instanceof PsiMethodCallExpression) {
328             newEnd = ((PsiMethodCallExpression)element).getMethodExpression().getTextRange().getEndOffset();
329           }
330           else if (element instanceof PsiNewExpression) {
331             final PsiJavaCodeReferenceElement classReference = ((PsiNewExpression)element).getClassReference();
332             if (classReference != null) {
333               newEnd = classReference.getTextRange().getEndOffset();
334             }
335           }
336           context.setReplacementOffset(newEnd);
337           element = element.getParent();
338         }
339       }
340     }
341
342     PsiElement lastElement = context.getFile().findElementAt(context.getStartOffset() - 1);
343     if (lastElement != null && lastElement.getText().equals("(") && lastElement.getParent() instanceof PsiParenthesizedExpression) {
344       // don't trim dummy identifier or we won't be able to determine the type of the expression after '(' 
345       // which is needed to insert correct cast
346       return;
347     }
348     context.setDummyIdentifier(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED);
349   }
350 }