| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.codeInsight.completion; |
| |
| import com.intellij.codeInsight.completion.util.MethodParenthesesHandler; |
| import com.intellij.codeInsight.lookup.*; |
| import com.intellij.codeInsight.lookup.impl.JavaElementLookupRenderer; |
| import com.intellij.featureStatistics.FeatureUsageTracker; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.util.ClassConditionKey; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** |
| * @author peter |
| */ |
| public class JavaMethodCallElement extends LookupItem<PsiMethod> implements TypedLookupItem, StaticallyImportable { |
| public static final ClassConditionKey<JavaMethodCallElement> CLASS_CONDITION_KEY = ClassConditionKey.create(JavaMethodCallElement.class); |
| @Nullable private final PsiClass myContainingClass; |
| private final PsiMethod myMethod; |
| private final MemberLookupHelper myHelper; |
| private PsiSubstitutor myInferenceSubstitutor = PsiSubstitutor.EMPTY; |
| private boolean myMayNeedExplicitTypeParameters; |
| |
| public JavaMethodCallElement(@NotNull PsiMethod method) { |
| this(method, method.getName()); |
| } |
| |
| public JavaMethodCallElement(@NotNull PsiMethod method, String methodName) { |
| super(method, methodName); |
| myMethod = method; |
| myHelper = null; |
| myContainingClass = method.getContainingClass(); |
| } |
| |
| public JavaMethodCallElement(PsiMethod method, boolean shouldImportStatic, boolean mergedOverloads) { |
| super(method, method.getName()); |
| myMethod = method; |
| myContainingClass = method.getContainingClass(); |
| myHelper = new MemberLookupHelper(method, myContainingClass, shouldImportStatic, mergedOverloads); |
| if (!shouldImportStatic) { |
| forceQualify(); |
| } |
| } |
| |
| @Override |
| public PsiType getType() { |
| return getSubstitutor().substitute(getInferenceSubstitutor().substitute(getObject().getReturnType())); |
| } |
| |
| public void setInferenceSubstitutor(@NotNull final PsiSubstitutor substitutor, PsiElement place) { |
| myInferenceSubstitutor = substitutor; |
| myMayNeedExplicitTypeParameters = mayNeedTypeParameters(place); |
| } |
| |
| @NotNull |
| public PsiSubstitutor getSubstitutor() { |
| final PsiSubstitutor substitutor = (PsiSubstitutor)getAttribute(SUBSTITUTOR); |
| return substitutor == null ? PsiSubstitutor.EMPTY : substitutor; |
| } |
| |
| @NotNull |
| public PsiSubstitutor getInferenceSubstitutor() { |
| return myInferenceSubstitutor; |
| } |
| |
| @Override |
| public void setShouldBeImported(boolean shouldImportStatic) { |
| myHelper.setShouldBeImported(shouldImportStatic); |
| } |
| |
| @Override |
| public boolean canBeImported() { |
| return myHelper != null; |
| } |
| |
| @Override |
| public boolean willBeImported() { |
| return canBeImported() && myHelper.willBeImported(); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (!(o instanceof JavaMethodCallElement)) return false; |
| if (!super.equals(o)) return false; |
| |
| return myInferenceSubstitutor.equals(((JavaMethodCallElement)o).myInferenceSubstitutor); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = super.hashCode(); |
| result = 31 * result + myInferenceSubstitutor.hashCode(); |
| return result; |
| } |
| |
| @Override |
| public void handleInsert(InsertionContext context) { |
| final Document document = context.getDocument(); |
| final PsiFile file = context.getFile(); |
| final PsiMethod method = getObject(); |
| |
| final LookupElement[] allItems = context.getElements(); |
| final boolean overloadsMatter = allItems.length == 1 && getUserData(FORCE_SHOW_SIGNATURE_ATTR) == null; |
| final boolean hasParams = MethodParenthesesHandler.hasParams(this, allItems, overloadsMatter, method); |
| JavaCompletionUtil.insertParentheses(context, this, overloadsMatter, hasParams); |
| |
| final int startOffset = context.getStartOffset(); |
| final OffsetKey refStart = context.trackOffset(startOffset, true); |
| if (shouldInsertTypeParameters() && mayNeedTypeParameters(context.getFile().findElementAt(context.getStartOffset()))) { |
| qualifyMethodCall(file, startOffset, document); |
| insertExplicitTypeParameters(context, refStart); |
| } |
| else if (myHelper != null || getAttribute(FORCE_QUALIFY) != null) { |
| context.commitDocument(); |
| if (myHelper != null && willBeImported()) { |
| final PsiReferenceExpression ref = PsiTreeUtil.findElementOfClassAtOffset(file, startOffset, PsiReferenceExpression.class, false); |
| if (ref != null && myContainingClass != null && !ref.isReferenceTo(method)) { |
| ref.bindToElementViaStaticImport(myContainingClass); |
| } |
| return; |
| } |
| |
| qualifyMethodCall(file, startOffset, document); |
| } |
| |
| final PsiType type = method.getReturnType(); |
| if (context.getCompletionChar() == '!' && type != null && PsiType.BOOLEAN.isAssignableFrom(type)) { |
| context.setAddCompletionChar(false); |
| context.commitDocument(); |
| final int offset = context.getOffset(refStart); |
| final PsiMethodCallExpression methodCall = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiMethodCallExpression.class, false); |
| if (methodCall != null) { |
| FeatureUsageTracker.getInstance().triggerFeatureUsed(CodeCompletionFeatures.EXCLAMATION_FINISH); |
| document.insertString(methodCall.getTextRange().getStartOffset(), "!"); |
| } |
| } |
| |
| } |
| |
| private boolean shouldInsertTypeParameters() { |
| return myMayNeedExplicitTypeParameters && !getInferenceSubstitutor().equals(PsiSubstitutor.EMPTY) && myMethod.getParameterList().getParametersCount() == 0; |
| } |
| |
| public static boolean mayNeedTypeParameters(final PsiElement leaf) { |
| if (PsiTreeUtil.getParentOfType(leaf, PsiExpressionList.class, true, PsiCodeBlock.class, PsiModifierListOwner.class) == null) { |
| if (PsiTreeUtil.getParentOfType(leaf, PsiConditionalExpression.class, true, PsiCodeBlock.class, PsiModifierListOwner.class) == null) { |
| return false; |
| } |
| } |
| if (leaf != null) { |
| final PsiElement parent = leaf.getParent(); |
| if (parent instanceof PsiReferenceExpression && ((PsiReferenceExpression)parent).getTypeParameters().length > 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private void insertExplicitTypeParameters(InsertionContext context, OffsetKey refStart) { |
| context.commitDocument(); |
| |
| final String typeParams = getTypeParamsText(false); |
| if (typeParams != null) { |
| context.getDocument().insertString(context.getOffset(refStart), typeParams); |
| JavaCompletionUtil.shortenReference(context.getFile(), context.getOffset(refStart)); |
| } |
| } |
| |
| private void qualifyMethodCall(PsiFile file, final int startOffset, final Document document) { |
| final PsiReference reference = file.findReferenceAt(startOffset); |
| if (reference instanceof PsiReferenceExpression && ((PsiReferenceExpression)reference).isQualified()) { |
| return; |
| } |
| |
| final PsiMethod method = getObject(); |
| if (!method.hasModifierProperty(PsiModifier.STATIC)) { |
| document.insertString(startOffset, "this."); |
| return; |
| } |
| |
| if (myContainingClass == null) return; |
| |
| document.insertString(startOffset, "."); |
| JavaCompletionUtil.insertClassReference(myContainingClass, file, startOffset); |
| } |
| |
| @Nullable |
| private String getTypeParamsText(boolean presentable) { |
| final PsiMethod method = getObject(); |
| final PsiSubstitutor substitutor = getInferenceSubstitutor(); |
| final PsiTypeParameter[] parameters = method.getTypeParameters(); |
| assert parameters.length > 0; |
| final StringBuilder builder = new StringBuilder("<"); |
| boolean first = true; |
| for (final PsiTypeParameter parameter : parameters) { |
| if (!first) builder.append(", "); |
| first = false; |
| PsiType type = substitutor.substitute(parameter); |
| if (type instanceof PsiWildcardType) { |
| type = ((PsiWildcardType)type).getExtendsBound(); |
| } |
| |
| if (type == null || type instanceof PsiCapturedWildcardType) return null; |
| if (type.equals(TypeConversionUtil.typeParameterErasure(parameter))) return null; |
| |
| final String text = presentable ? type.getPresentableText() : type.getCanonicalText(); |
| if (text.indexOf('?') >= 0) return null; |
| |
| builder.append(text); |
| } |
| return builder.append(">").toString(); |
| } |
| |
| @Override |
| public LookupItem<PsiMethod> forceQualify() { |
| if (myContainingClass != null) { |
| String className = myContainingClass.getName(); |
| if (className != null) { |
| addLookupStrings(className + "." + myMethod.getName()); |
| } |
| } |
| return super.forceQualify(); |
| } |
| |
| @Override |
| public boolean isValid() { |
| return super.isValid() && myInferenceSubstitutor.isValid() && getSubstitutor().isValid(); |
| } |
| |
| @Override |
| public void renderElement(LookupElementPresentation presentation) { |
| presentation.setIcon(DefaultLookupItemRenderer.getRawIcon(this, presentation.isReal())); |
| |
| presentation.setStrikeout(JavaElementLookupRenderer.isToStrikeout(this)); |
| presentation.setItemTextBold(getAttribute(HIGHLIGHTED_ATTR) != null); |
| |
| MemberLookupHelper helper = myHelper != null ? myHelper : new MemberLookupHelper(myMethod, myContainingClass, false, false); |
| final Boolean qualify = getAttribute(FORCE_QUALIFY) != null ? Boolean.TRUE : myHelper == null ? Boolean.FALSE : null; |
| helper.renderElement(presentation, qualify, getSubstitutor()); |
| |
| if (shouldInsertTypeParameters()) { |
| String typeParamsText = getTypeParamsText(true); |
| if (typeParamsText != null) { |
| if (typeParamsText.length() > 10) { |
| typeParamsText = typeParamsText.substring(0, 10) + "...>"; |
| } |
| |
| String itemText = presentation.getItemText(); |
| assert itemText != null; |
| int i = itemText.indexOf('.'); |
| if (i > 0) { |
| presentation.setItemText(itemText.substring(0, i + 1) + typeParamsText + itemText.substring(i + 1)); |
| } |
| } |
| } |
| |
| } |
| } |