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