1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInsight.completion;
4 import com.intellij.codeInsight.AutoPopupController;
5 import com.intellij.codeInsight.ExpectedTypesProvider;
6 import com.intellij.codeInsight.NullableNotNullManager;
7 import com.intellij.codeInsight.daemon.impl.analysis.HighlightingFeature;
8 import com.intellij.codeInsight.lookup.Lookup;
9 import com.intellij.codeInsight.lookup.LookupElement;
10 import com.intellij.codeInsight.lookup.PsiTypeLookupItem;
11 import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
12 import com.intellij.openapi.editor.Editor;
13 import com.intellij.openapi.editor.EditorModificationUtilEx;
14 import com.intellij.openapi.project.Project;
15 import com.intellij.pom.java.LanguageLevel;
16 import com.intellij.psi.*;
17 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
18 import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
19 import com.intellij.psi.filters.FilterPositionUtil;
20 import com.intellij.psi.impl.source.codeStyle.ImportHelper;
21 import com.intellij.psi.javadoc.PsiDocComment;
22 import com.intellij.psi.javadoc.PsiDocTag;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import com.intellij.psi.util.PsiUtil;
25 import com.intellij.util.containers.ContainerUtil;
26 import com.siyeh.ig.psiutils.MethodCallUtils;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
30 import static com.intellij.psi.codeStyle.JavaCodeStyleSettings.*;
35 class JavaClassNameInsertHandler implements InsertHandler<JavaPsiClassReferenceElement> {
36 static final InsertHandler<JavaPsiClassReferenceElement> JAVA_CLASS_INSERT_HANDLER = new JavaClassNameInsertHandler();
39 public void handleInsert(@NotNull final InsertionContext context, @NotNull final JavaPsiClassReferenceElement item) {
40 int offset = context.getTailOffset() - 1;
41 final PsiFile file = context.getFile();
42 PsiImportStatementBase importStatement = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiImportStatementBase.class, false);
43 if (importStatement != null) {
44 PsiJavaCodeReferenceElement ref = findJavaReference(file, offset);
45 String qname = item.getQualifiedName();
46 if (qname != null && (ref == null || !qname.equals(ref.getCanonicalText()))) {
47 AllClassesGetter.INSERT_FQN.handleInsert(context, item);
49 if (importStatement instanceof PsiImportStaticStatement) {
50 context.setAddCompletionChar(false);
51 EditorModificationUtilEx.insertStringAtCaret(context.getEditor(), ".");
56 PsiElement position = file.findElementAt(offset);
57 PsiJavaCodeReferenceElement ref = position != null && position.getParent() instanceof PsiJavaCodeReferenceElement ?
58 (PsiJavaCodeReferenceElement) position.getParent() : null;
59 PsiClass psiClass = item.getObject();
60 SmartPsiElementPointer<PsiClass> classPointer = SmartPointerManager.createPointer(psiClass);
61 final Project project = context.getProject();
63 final Editor editor = context.getEditor();
64 final char c = context.getCompletionChar();
66 context.setLaterRunnable(() -> new CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(project, editor));
67 } else if (c == '.' && PsiTreeUtil.getParentOfType(position, PsiParameterList.class) == null) {
68 AutoPopupController.getInstance(context.getProject()).autoPopupMemberLookup(context.getEditor(), null);
71 String qname = psiClass.getQualifiedName();
72 if (qname != null && PsiTreeUtil.getParentOfType(position, PsiDocComment.class, false) != null &&
73 (ref == null || !ref.isQualified()) &&
74 shouldInsertFqnInJavadoc(item, file)) {
75 context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), qname);
79 if (ref != null && PsiTreeUtil.getParentOfType(position, PsiDocTag.class) != null && ref.isReferenceTo(psiClass)) {
83 OffsetKey refEnd = context.trackOffset(context.getTailOffset(), true);
85 boolean fillTypeArgs = context.getCompletionChar() == '<';
87 context.setAddCompletionChar(false);
90 PsiTypeLookupItem.addImportForItem(context, psiClass);
91 if (!context.getOffsetMap().containsOffset(refEnd)) {
95 context.setTailOffset(context.getOffset(refEnd));
96 refEnd = context.trackOffset(context.getTailOffset(), false);
98 context.commitDocument();
100 // Restore elements after commit
101 position = file.findElementAt(context.getTailOffset() - 1);
102 ref = position != null && position.getParent() instanceof PsiJavaCodeReferenceElement ?
103 (PsiJavaCodeReferenceElement)position.getParent() : null;
105 if (c == '!' || c == '?') {
106 context.setAddCompletionChar(false);
107 if (ref != null && !(ref instanceof PsiReferenceExpression) &&
108 !ref.textContains('@') && !(ref.getParent() instanceof PsiAnnotation)) {
109 NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
110 String annoName = c == '!' ? manager.getDefaultNotNull() : manager.getDefaultNullable();
111 PsiClass cls = JavaPsiFacade.getInstance(project).findClass(annoName, file.getResolveScope());
113 PsiJavaCodeReferenceElement newRef =
114 JavaPsiFacade.getElementFactory(project).createReferenceFromText('@' + annoName + ' ' + ref.getText(), ref);
115 JavaCodeStyleManager.getInstance(project).shortenClassReferences(ref.replace(newRef));
120 if (ref != null && HighlightingFeature.PATTERNS.isAvailable(ref) && psiClass.getTypeParameters().length > 0) {
121 PsiExpression instanceOfOperand = JavaCompletionUtil.getInstanceOfOperand(ref);
122 if (instanceOfOperand != null) {
123 PsiClassType origType = JavaPsiFacade.getElementFactory(project).createType(psiClass);
124 PsiType generified = DfaPsiUtil.tryGenerify(instanceOfOperand, origType);
125 if (generified != null && generified != origType) {
126 String completeTypeText = generified.getCanonicalText();
127 PsiJavaCodeReferenceElement newRef =
128 JavaPsiFacade.getElementFactory(project).createReferenceFromText(completeTypeText, ref);
129 PsiElement resultingRef = JavaCodeStyleManager.getInstance(project).shortenClassReferences(ref.replace(newRef));
130 context.getEditor().getCaretModel().moveToOffset(resultingRef.getTextRange().getEndOffset());
136 psiClass = classPointer.dereference();
138 if (item.getUserData(JavaChainLookupElement.CHAIN_QUALIFIER) == null &&
139 shouldInsertParentheses(file.findElementAt(context.getTailOffset() - 1))) {
140 if (context.getCompletionChar() == Lookup.REPLACE_SELECT_CHAR) {
141 overwriteTopmostReference(context);
142 context.commitDocument();
144 if (psiClass != null && ConstructorInsertHandler.insertParentheses(context, item, psiClass, false)) {
145 fillTypeArgs |= psiClass.hasTypeParameters() && PsiUtil.getLanguageLevel(file).isAtLeast(LanguageLevel.JDK_1_5);
148 else if (insertingAnnotation(context, item)) {
149 if (psiClass != null && shouldHaveAnnotationParameters(psiClass)) {
150 JavaCompletionUtil.insertParentheses(context, item, false, true);
152 if (context.getCompletionChar() == Lookup.NORMAL_SELECT_CHAR || context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR) {
153 CharSequence text = context.getDocument().getCharsSequence();
154 int tail = context.getTailOffset();
155 if (text.length() > tail && Character.isLetter(text.charAt(tail))) {
156 context.getDocument().insertString(tail, " ");
161 if (fillTypeArgs && context.getCompletionChar() != '(') {
162 JavaCompletionUtil.promptTypeArgs(context, context.getOffset(refEnd));
164 else if (context.getCompletionChar() == Lookup.COMPLETE_STATEMENT_SELECT_CHAR &&
165 psiClass != null && psiClass.getTypeParameters().length == 1 &&
166 PsiUtil.getLanguageLevel(file).isAtLeast(LanguageLevel.JDK_1_5)) {
167 wrapFollowingTypeInGenerics(context, context.getOffset(refEnd));
171 private static void wrapFollowingTypeInGenerics(InsertionContext context, int refEnd) {
172 PsiTypeElement typeElem = PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), refEnd, PsiTypeElement.class, false);
173 if (typeElem != null) {
174 int typeEnd = typeElem.getTextRange().getEndOffset();
175 context.getDocument().insertString(typeEnd, ">");
176 context.getEditor().getCaretModel().moveToOffset(typeEnd + 1);
177 context.getDocument().insertString(refEnd, "<");
178 context.setAddCompletionChar(false);
182 @Nullable static PsiJavaCodeReferenceElement findJavaReference(@NotNull PsiFile file, int offset) {
183 return PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiJavaCodeReferenceElement.class, false);
186 static void overwriteTopmostReference(InsertionContext context) {
187 context.commitDocument();
188 PsiJavaCodeReferenceElement ref = findJavaReference(context.getFile(), context.getTailOffset() - 1);
190 while (ref.getParent() instanceof PsiJavaCodeReferenceElement) ref = (PsiJavaCodeReferenceElement)ref.getParent();
191 context.getDocument().deleteString(context.getTailOffset(), ref.getTextRange().getEndOffset());
195 private static boolean shouldInsertFqnInJavadoc(@NotNull JavaPsiClassReferenceElement item, @NotNull PsiFile file) {
196 JavaCodeStyleSettings javaSettings = getInstance(file);
198 switch (javaSettings.CLASS_NAMES_IN_JAVADOC) {
199 case FULLY_QUALIFY_NAMES_ALWAYS:
201 case SHORTEN_NAMES_ALWAYS_AND_ADD_IMPORT:
203 case FULLY_QUALIFY_NAMES_IF_NOT_IMPORTED:
204 if (file instanceof PsiJavaFile) {
205 PsiJavaFile javaFile = ((PsiJavaFile)file);
206 return item.getQualifiedName() != null && !ImportHelper.isAlreadyImported(javaFile, item.getQualifiedName());
213 private static boolean shouldInsertParentheses(PsiElement position) {
214 final PsiJavaCodeReferenceElement ref = PsiTreeUtil.getParentOfType(position, PsiJavaCodeReferenceElement.class);
219 final PsiReferenceParameterList parameterList = ref.getParameterList();
220 if (parameterList != null && parameterList.getTextLength() > 0) {
224 final PsiElement prevElement = FilterPositionUtil.searchNonSpaceNonCommentBack(ref);
225 if (prevElement != null && prevElement.getParent() instanceof PsiNewExpression) {
226 return !isArrayTypeExpected((PsiExpression)prevElement.getParent());
232 static boolean isArrayTypeExpected(PsiExpression expr) {
233 return ContainerUtil.exists(ExpectedTypesProvider.getExpectedTypes(expr, true),
235 if (info.getType() instanceof PsiArrayType) {
236 PsiMethod method = info.getCalledMethod();
237 return method == null || !method.isVarArgs() || !(expr.getParent() instanceof PsiExpressionList) ||
238 MethodCallUtils.getParameterForArgument(expr) != null;
244 private static boolean insertingAnnotation(InsertionContext context, LookupElement item) {
245 final Object obj = item.getObject();
246 if (!(obj instanceof PsiClass) || !((PsiClass)obj).isAnnotationType()) return false;
248 PsiElement leaf = context.getFile().findElementAt(context.getStartOffset());
249 PsiAnnotation anno = PsiTreeUtil.getParentOfType(leaf, PsiAnnotation.class);
250 return anno != null && PsiTreeUtil.isAncestor(anno.getNameReferenceElement(), leaf, false);
253 static boolean shouldHaveAnnotationParameters(PsiClass annoClass) {
254 for (PsiMethod m : annoClass.getMethods()) {
255 if (!PsiUtil.isAnnotationMethod(m)) continue;
256 if (((PsiAnnotationMethod)m).getDefaultValue() == null) return true;