0cb4dbe86e39a286d899ad00f41fb64e33bbe4fd
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / JavaClassNameInsertHandler.java
1 /*
2  * Copyright 2000-2010 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.AutoPopupController;
19 import com.intellij.codeInsight.ExpectedTypeInfo;
20 import com.intellij.codeInsight.ExpectedTypesProvider;
21 import com.intellij.codeInsight.lookup.LookupElement;
22 import com.intellij.codeInsight.lookup.PsiTypeLookupItem;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.editor.Document;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.editor.ex.EditorEx;
27 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.pom.java.LanguageLevel;
30 import com.intellij.psi.*;
31 import com.intellij.psi.filters.FilterPositionUtil;
32 import com.intellij.psi.javadoc.PsiDocTag;
33 import com.intellij.psi.util.PsiTreeUtil;
34 import com.intellij.psi.util.PsiUtil;
35 import com.intellij.util.containers.hash.HashSet;
36
37 import java.util.Set;
38
39 /**
40 * @author peter
41 */
42 class JavaClassNameInsertHandler implements InsertHandler<JavaPsiClassReferenceElement> {
43   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaClassNameInsertHandler");
44   static final InsertHandler<JavaPsiClassReferenceElement> JAVA_CLASS_INSERT_HANDLER = new JavaClassNameInsertHandler();
45
46   public void handleInsert(final InsertionContext context, final JavaPsiClassReferenceElement item) {
47     final char c = context.getCompletionChar();
48
49     int offset = context.getTailOffset() - 1;
50     final PsiFile file = context.getFile();
51     if (PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiImportStatementBase.class, false) != null) {
52       final PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiJavaCodeReferenceElement.class, false);
53       final String qname = item.getQualifiedName();
54       if (qname != null && (ref == null || !qname.equals(ref.getCanonicalText()))) {
55         AllClassesGetter.INSERT_FQN.handleInsert(context, item);
56       }
57       return;
58     }
59
60     PsiElement position = file.findElementAt(offset);
61     PsiClass psiClass = item.getObject();
62     final Project project = context.getProject();
63     final boolean annotation = insertingAnnotation(context, item);
64
65     final Editor editor = context.getEditor();
66     if (c == '#') {
67       context.setLaterRunnable(new Runnable() {
68         public void run() {
69           new CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(project, editor);
70         }
71       });
72     } else if (c == '.' && PsiTreeUtil.getParentOfType(position, PsiParameterList.class) == null) {
73       AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null);
74     }
75
76     if (position != null) {
77       PsiElement parent = position.getParent();
78       if (parent instanceof PsiJavaCodeReferenceElement) {
79         final PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)parent;
80         if (PsiTreeUtil.getParentOfType(position, PsiDocTag.class) != null && ref.isReferenceTo(psiClass)) {
81           return;
82         }
83       }
84     }
85
86     OffsetKey refEnd = context.trackOffset(context.getTailOffset(), false);
87
88     boolean fillTypeArgs = context.getCompletionChar() == '<';
89     if (fillTypeArgs) {
90       context.setAddCompletionChar(false);
91     }
92
93     PsiTypeLookupItem.addImportForItem(context, psiClass);
94
95     if (shouldInsertParentheses(psiClass, file.findElementAt(context.getTailOffset() - 1))) {
96       if (ConstructorInsertHandler.insertParentheses(context, item, psiClass, false)) {
97         fillTypeArgs |= psiClass.hasTypeParameters() && PsiUtil.getLanguageLevel(file).isAtLeast(LanguageLevel.JDK_1_5);
98       }
99     }
100     else if (insertingAnnotationWithParameters(context, item)) {
101       JavaCompletionUtil.insertParentheses(context, item, false, true);
102       AutoPopupController.getInstance(project).autoPopupParameterInfo(editor, null);
103     }
104
105     if (annotation) {
106       // Check if someone inserts annotation class that require @
107       PsiElement elementAt = file.findElementAt(context.getStartOffset());
108       final PsiElement parentElement = elementAt != null ? elementAt.getParent():null;
109
110       if (elementAt instanceof PsiIdentifier &&
111           (PsiTreeUtil.getParentOfType(elementAt, PsiAnnotationParameterList.class) != null ||
112            parentElement instanceof PsiErrorElement && parentElement.getParent() instanceof PsiJavaFile // top level annotation without @
113           )
114           && isAtTokenNeeded(context)) {
115         int expectedOffsetForAtToken = elementAt.getTextRange().getStartOffset();
116         context.getDocument().insertString(expectedOffsetForAtToken, "@");
117       }
118     }
119
120     if (fillTypeArgs && context.getCompletionChar() != '(') {
121       JavaCompletionUtil.promptTypeArgs(context, context.getOffset(refEnd));
122     }
123   }
124
125   private static boolean shouldInsertParentheses(PsiClass psiClass, PsiElement position) {
126     final PsiJavaCodeReferenceElement ref = PsiTreeUtil.getParentOfType(position, PsiJavaCodeReferenceElement.class);
127     if (ref == null) {
128       return false;
129     }
130
131     final PsiReferenceParameterList parameterList = ref.getParameterList();
132     if (parameterList != null && parameterList.getTextLength() > 0) {
133       return false;
134     }
135
136     final PsiElement prevElement = FilterPositionUtil.searchNonSpaceNonCommentBack(ref);
137     if (prevElement != null && prevElement.getParent() instanceof PsiNewExpression) {
138
139       Set<PsiType> expectedTypes = new HashSet<PsiType>();
140       for (ExpectedTypeInfo info : ExpectedTypesProvider.getExpectedTypes((PsiExpression)prevElement.getParent(), true)) {
141         expectedTypes.add(info.getType());
142       }
143
144       return JavaCompletionUtil.isDefinitelyExpected(psiClass, expectedTypes, position);
145     }
146
147     return false;
148   }
149
150   private static boolean insertingAnnotationWithParameters(InsertionContext context, LookupElement item) {
151     if(insertingAnnotation(context, item)) {
152       final Document document = context.getEditor().getDocument();
153       PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
154       PsiElement elementAt = context.getFile().findElementAt(context.getStartOffset());
155       if (elementAt instanceof PsiIdentifier) {
156         final PsiModifierListOwner parent = PsiTreeUtil.getParentOfType(elementAt, PsiModifierListOwner.class, false, PsiCodeBlock.class);
157         if (parent != null) {
158           for (PsiMethod m : ((PsiClass)item.getObject()).getMethods()) {
159             if (!(m instanceof PsiAnnotationMethod)) continue;
160             final PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod)m).getDefaultValue();
161             if (defaultValue == null) return true;
162           }
163         }
164       }
165     }
166     return false;
167   }
168
169   private static boolean insertingAnnotation(InsertionContext context, LookupElement item) {
170     final Object obj = item.getObject();
171     if (!(obj instanceof PsiClass) || !((PsiClass)obj).isAnnotationType()) return false;
172
173     final Document document = context.getEditor().getDocument();
174     PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
175     final int offset = context.getStartOffset();
176
177     final PsiFile file = context.getFile();
178
179     if (PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiImportStatement.class, false) != null) return false;
180
181     //outside of any class: we are surely inserting an annotation
182     if (PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiClass.class, false) == null) return true;
183
184     //the easiest check that there's a @ before the identifier
185     return PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiAnnotation.class, false) != null;
186
187   }
188
189   private static boolean isAtTokenNeeded(InsertionContext myContext) {
190     HighlighterIterator iterator = ((EditorEx)myContext.getEditor()).getHighlighter().createIterator(myContext.getStartOffset());
191     LOG.assertTrue(iterator.getTokenType() == JavaTokenType.IDENTIFIER);
192     iterator.retreat();
193     if (iterator.getTokenType() == TokenType.WHITE_SPACE) iterator.retreat();
194     return iterator.getTokenType() != JavaTokenType.AT && iterator.getTokenType() != JavaTokenType.DOT;
195   }
196 }