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