an initial version of IDEABKL-5496 Auto-filling the actual Java call arguments
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / JavaMethodCallElement.java
1 /*
2  * Copyright 2000-2009 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.completion.util.MethodParenthesesHandler;
19 import com.intellij.codeInsight.lookup.*;
20 import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer;
21 import com.intellij.codeInsight.template.Template;
22 import com.intellij.codeInsight.template.TemplateManager;
23 import com.intellij.codeInsight.template.impl.ConstantNode;
24 import com.intellij.codeInsight.template.impl.MacroCallNode;
25 import com.intellij.codeInsight.template.macro.CompleteMacro;
26 import com.intellij.featureStatistics.FeatureUsageTracker;
27 import com.intellij.openapi.editor.Document;
28 import com.intellij.openapi.util.ClassConditionKey;
29 import com.intellij.openapi.util.registry.Registry;
30 import com.intellij.openapi.util.text.StringUtil;
31 import com.intellij.pom.java.LanguageLevel;
32 import com.intellij.psi.*;
33 import com.intellij.psi.util.PsiTreeUtil;
34 import com.intellij.psi.util.PsiUtil;
35 import com.intellij.psi.util.TypeConversionUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 /**
40  * @author peter
41  */
42 public class JavaMethodCallElement extends LookupItem<PsiMethod> implements TypedLookupItem, StaticallyImportable {
43   public static final ClassConditionKey<JavaMethodCallElement> CLASS_CONDITION_KEY = ClassConditionKey.create(JavaMethodCallElement.class);
44   @Nullable private final PsiClass myContainingClass;
45   private final PsiMethod myMethod;
46   private final MemberLookupHelper myHelper;
47   private PsiSubstitutor myQualifierSubstitutor = PsiSubstitutor.EMPTY;
48   private PsiSubstitutor myInferenceSubstitutor = PsiSubstitutor.EMPTY;
49   private boolean myMayNeedExplicitTypeParameters;
50   private String myForcedQualifier = "";
51
52   public JavaMethodCallElement(@NotNull PsiMethod method) {
53     this(method, method.getName());
54   }
55
56   public JavaMethodCallElement(@NotNull PsiMethod method, String methodName) {
57     super(method, methodName);
58     myMethod = method;
59     myHelper = null;
60     myContainingClass = method.getContainingClass();
61   }
62
63   public JavaMethodCallElement(PsiMethod method, boolean shouldImportStatic, boolean mergedOverloads) {
64     super(method, method.getName());
65     myMethod = method;
66     myContainingClass = method.getContainingClass();
67     myHelper = new MemberLookupHelper(method, myContainingClass, shouldImportStatic, mergedOverloads);
68     if (!shouldImportStatic) {
69       if (myContainingClass != null) {
70         String className = myContainingClass.getName();
71         if (className != null) {
72           addLookupStrings(className + "." + myMethod.getName());
73         }
74       }
75     }
76   }
77
78   void setForcedQualifier(@NotNull String forcedQualifier) {
79     myForcedQualifier = forcedQualifier;
80     setLookupString(forcedQualifier + getLookupString());
81   }
82
83   @Override
84   public PsiType getType() {
85     return getSubstitutor().substitute(getInferenceSubstitutor().substitute(getObject().getReturnType()));
86   }
87
88   public void setInferenceSubstitutor(@NotNull final PsiSubstitutor substitutor, PsiElement place) {
89     myInferenceSubstitutor = substitutor;
90     myMayNeedExplicitTypeParameters = mayNeedTypeParameters(place);
91   }
92
93   public JavaMethodCallElement setQualifierSubstitutor(@NotNull PsiSubstitutor qualifierSubstitutor) {
94     myQualifierSubstitutor = qualifierSubstitutor;
95     return this;
96   }
97
98   @NotNull
99   public PsiSubstitutor getSubstitutor() {
100     return myQualifierSubstitutor;
101   }
102
103   @NotNull
104   public PsiSubstitutor getInferenceSubstitutor() {
105     return myInferenceSubstitutor;
106   }
107
108   @Override
109   public void setShouldBeImported(boolean shouldImportStatic) {
110     myHelper.setShouldBeImported(shouldImportStatic);
111   }
112
113   @Override
114   public boolean canBeImported() {
115     return myHelper != null;
116   }
117
118   @Override
119   public boolean willBeImported() {
120     return canBeImported() && myHelper.willBeImported();
121   }
122
123   @Override
124   public boolean equals(Object o) {
125     if (this == o) return true;
126     if (!(o instanceof JavaMethodCallElement)) return false;
127     if (!super.equals(o)) return false;
128
129     return myInferenceSubstitutor.equals(((JavaMethodCallElement)o).myInferenceSubstitutor);
130   }
131
132   @Override
133   public int hashCode() {
134     int result = super.hashCode();
135     result = 31 * result + myInferenceSubstitutor.hashCode();
136     return result;
137   }
138
139   @Override
140   public void handleInsert(InsertionContext context) {
141     final Document document = context.getDocument();
142     final PsiFile file = context.getFile();
143     final PsiMethod method = getObject();
144
145     final LookupElement[] allItems = context.getElements();
146     final boolean overloadsMatter = allItems.length == 1 && getUserData(JavaCompletionUtil.FORCE_SHOW_SIGNATURE_ATTR) == null;
147     final boolean hasParams = MethodParenthesesHandler.hasParams(this, allItems, overloadsMatter, method);
148     JavaCompletionUtil.insertParentheses(context, this, overloadsMatter, hasParams);
149
150     final int startOffset = context.getStartOffset();
151     final OffsetKey refStart = context.trackOffset(startOffset, true);
152     if (shouldInsertTypeParameters() && mayNeedTypeParameters(context.getFile().findElementAt(context.getStartOffset()))) {
153       qualifyMethodCall(file, startOffset, document);
154       insertExplicitTypeParameters(context, refStart);
155     }
156     else if (myHelper != null) {
157       context.commitDocument();
158       importOrQualify(document, file, method, startOffset);
159     }
160
161     final PsiType type = method.getReturnType();
162     if (context.getCompletionChar() == '!' && type != null && PsiType.BOOLEAN.isAssignableFrom(type)) {
163       context.setAddCompletionChar(false);
164       context.commitDocument();
165       final int offset = context.getOffset(refStart);
166       final PsiMethodCallExpression methodCall = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiMethodCallExpression.class, false);
167       if (methodCall != null) {
168         FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EXCLAMATION_FINISH);
169         document.insertString(methodCall.getTextRange().getStartOffset(), "!");
170       }
171     }
172
173     if (hasParams && Registry.is("java.completion.argument.live.template")) {
174       startArgumentLiveTemplate(context, method);
175     }
176   }
177
178   private void importOrQualify(Document document, PsiFile file, PsiMethod method, int startOffset) {
179     if (willBeImported()) {
180       final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiReferenceExpression.class, false);
181       if (ref != null && myContainingClass != null && !ref.isReferenceTo(method)) {
182         ref.bindToElementViaStaticImport(myContainingClass);
183       }
184       return;
185     }
186
187     qualifyMethodCall(file, startOffset, document);
188   }
189
190   private static void startArgumentLiveTemplate(InsertionContext context, PsiMethod method) {
191     TemplateManager manager = TemplateManager.getInstance(method.getProject());
192     Template template = manager.createTemplate("", "");
193     PsiParameter[] parameters = method.getParameterList().getParameters();
194     for (int i = 0; i < parameters.length; i++) {
195       if (i > 0) {
196         template.addTextSegment(", ");
197       }
198       String name = StringUtil.notNullize(parameters[i].getName());
199       template.addVariable(name, new MacroCallNode(new CompleteMacro()), new ConstantNode(name), true);
200     }
201
202     manager.startTemplate(context.getEditor(), template);
203   }
204
205   private boolean shouldInsertTypeParameters() {
206     return myMayNeedExplicitTypeParameters && !getInferenceSubstitutor().equals(PsiSubstitutor.EMPTY) && myMethod.getParameterList().getParametersCount() == 0;
207   }
208
209   public static boolean mayNeedTypeParameters(@Nullable final PsiElement leaf) {
210     if (PsiTreeUtil.getParentOfType(leaf, PsiExpressionList.class, true, PsiCodeBlock.class, PsiModifierListOwner.class) == null) {
211       if (PsiTreeUtil.getParentOfType(leaf, PsiConditionalExpression.class, true, PsiCodeBlock.class, PsiModifierListOwner.class) == null) {
212         return false;
213       }
214     }
215
216     if (PsiUtil.getLanguageLevel(leaf).isAtLeast(LanguageLevel.JDK_1_8)) return false;
217
218     final PsiElement parent = leaf.getParent();
219     if (parent instanceof PsiReferenceExpression && ((PsiReferenceExpression)parent).getTypeParameters().length > 0) {
220       return false;
221     }
222     return true;
223   }
224
225   private void insertExplicitTypeParameters(InsertionContext context, OffsetKey refStart) {
226     context.commitDocument();
227
228     final String typeParams = getTypeParamsText(false);
229     if (typeParams != null) {
230       context.getDocument().insertString(context.getOffset(refStart), typeParams);
231       JavaCompletionUtil.shortenReference(context.getFile(), context.getOffset(refStart));
232     }
233   }
234
235   private void qualifyMethodCall(PsiFile file, final int startOffset, final Document document) {
236     final PsiReference reference = file.findReferenceAt(startOffset);
237     if (reference instanceof PsiReferenceExpression && ((PsiReferenceExpression)reference).isQualified()) {
238       return;
239     }
240
241     final PsiMethod method = getObject();
242     if (!method.hasModifierProperty(PsiModifier.STATIC)) {
243       document.insertString(startOffset, "this.");
244       return;
245     }
246
247     if (myContainingClass == null) return;
248
249     document.insertString(startOffset, ".");
250     JavaCompletionUtil.insertClassReference(myContainingClass, file, startOffset);
251   }
252
253   @Nullable
254   private String getTypeParamsText(boolean presentable) {
255     final PsiMethod method = getObject();
256     final PsiSubstitutor substitutor = getInferenceSubstitutor();
257     final PsiTypeParameter[] parameters = method.getTypeParameters();
258     assert parameters.length > 0;
259     final StringBuilder builder = new StringBuilder("<");
260     boolean first = true;
261     for (final PsiTypeParameter parameter : parameters) {
262       if (!first) builder.append(", ");
263       first = false;
264       PsiType type = substitutor.substitute(parameter);
265       if (type instanceof PsiWildcardType) {
266         type = ((PsiWildcardType)type).getExtendsBound();
267       }
268
269       if (type == null || type instanceof PsiCapturedWildcardType) return null;
270       if (type.equals(TypeConversionUtil.typeParameterErasure(parameter))) return null;
271
272       final String text = presentable ? type.getPresentableText() : type.getCanonicalText();
273       if (text.indexOf('?') >= 0) return null;
274
275       builder.append(text);
276     }
277     return builder.append(">").toString();
278   }
279
280   @Override
281   public boolean isValid() {
282     return super.isValid() && myInferenceSubstitutor.isValid() && getSubstitutor().isValid();
283   }
284
285   @Override
286   public void renderElement(LookupElementPresentation presentation) {
287     presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(this, presentation.isReal()));
288
289     presentation.setStrikeout(JavaElementLookupRenderer.isToStrikeout(this));
290
291     MemberLookupHelper helper = myHelper != null ? myHelper : new MemberLookupHelper(myMethod, myContainingClass, false, false);
292     helper.renderElement(presentation, myHelper != null, myHelper != null && !myHelper.willBeImported(), getSubstitutor());
293     if (!myForcedQualifier.isEmpty()) {
294       presentation.setItemText(myForcedQualifier + presentation.getItemText());
295     }
296
297     if (shouldInsertTypeParameters()) {
298       String typeParamsText = getTypeParamsText(true);
299       if (typeParamsText != null) {
300         if (typeParamsText.length() > 10) {
301           typeParamsText = typeParamsText.substring(0, 10) + "...>";
302         }
303
304         String itemText = presentation.getItemText();
305         assert itemText != null;
306         int i = itemText.indexOf('.');
307         if (i > 0) {
308           presentation.setItemText(itemText.substring(0, i + 1) + typeParamsText + itemText.substring(i + 1));
309         }
310       }
311     }
312     
313   }
314 }