diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefaultQuickFixProvider.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefaultQuickFixProvider.java index b5dc43e0f84a..7151726b0fb2 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefaultQuickFixProvider.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/DefaultQuickFixProvider.java @@ -39,6 +39,7 @@ public class DefaultQuickFixProvider extends UnresolvedReferenceQuickFixProvider public void registerFixes(@NotNull PsiJavaCodeReferenceElement ref, @NotNull QuickFixActionRegistrar registrar) { QuickFixFactory factory = QuickFixFactory.getInstance(); registrar.register(new ImportClassFix(ref)); + registrar.register(new StaticImportConstantFix(ref)); registrar.register(factory.createSetupJDKFix()); OrderEntryFix.registerFixes(registrar, ref); diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MyStaticMembersProcessor.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MyStaticMembersProcessor.java new file mode 100644 index 000000000000..1848382e04ec --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/MyStaticMembersProcessor.java @@ -0,0 +1,194 @@ +/* + * Copyright 2000-2016 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.daemon.impl.quickfix; + +import com.intellij.codeInsight.completion.JavaCompletionUtil; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.proximity.PsiProximityComparator; +import com.intellij.util.ArrayUtilRt; +import com.intellij.util.Processor; +import com.intellij.util.containers.LinkedMultiMap; +import com.intellij.util.containers.MultiMap; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +abstract class MyStaticMembersProcessor implements Processor { + private final MultiMap myDeprecated = new LinkedMultiMap(); + private final MultiMap mySuggestions = new LinkedMultiMap(); + + private final Map myPossibleClasses = new HashMap(); + + private final PsiElement myPlace; + private PsiType myExpectedType; + + protected MyStaticMembersProcessor(PsiElement place) { + myPlace = place; + myExpectedType = PsiType.NULL; + } + + protected abstract boolean isApplicable(T member, PsiElement place); + + @NotNull + public List getMembersToImport() { + final List list = new ArrayList(); + final List applicableList = new ArrayList(); + for (Map.Entry> methodEntry : mySuggestions.entrySet()) { + registerMember(methodEntry.getKey(), methodEntry.getValue(), list, applicableList); + } + + for (Map.Entry> deprecatedMethod : myDeprecated.entrySet()) { + registerMember(deprecatedMethod.getKey(), deprecatedMethod.getValue(), list, applicableList); + } + + List result = applicableList.isEmpty() ? list : applicableList; + for (int i = result.size() - 1; i >= 0; i--) { + ProgressManager.checkCanceled(); + T method = result.get(i); + // check for manually excluded + if (StaticImportMethodFix.isExcluded(method)) { + result.remove(i); + } + } + Collections.sort(result, new PsiProximityComparator(myPlace)); + return result; + } + + public PsiType getExpectedType() { + if (myExpectedType == PsiType.NULL) { + myExpectedType = getExpectedTypeInternal(); + } + return myExpectedType; + } + + private PsiType getExpectedTypeInternal() { + if (myPlace == null) return null; + final PsiElement parent = PsiUtil.skipParenthesizedExprUp(myPlace.getParent()); + + if (parent instanceof PsiVariable) { + if (myPlace.equals(PsiUtil.skipParenthesizedExprDown(((PsiVariable)parent).getInitializer()))) { + return ((PsiVariable)parent).getType(); + } + } + else if (parent instanceof PsiAssignmentExpression) { + if (myPlace.equals(PsiUtil.skipParenthesizedExprDown(((PsiAssignmentExpression)parent).getRExpression()))) { + return ((PsiAssignmentExpression)parent).getLExpression().getType(); + } + } + else if (parent instanceof PsiReturnStatement) { + final PsiElement psiElement = PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class, PsiMethod.class); + if (psiElement instanceof PsiLambdaExpression) { + return LambdaUtil.getFunctionalInterfaceReturnType(((PsiLambdaExpression)psiElement).getFunctionalInterfaceType()); + } + else if (psiElement instanceof PsiMethod) { + return ((PsiMethod)psiElement).getReturnType(); + } + } + else if (parent instanceof PsiExpressionList) { + final PsiElement pParent = parent.getParent(); + if (pParent instanceof PsiCallExpression && parent.equals(((PsiCallExpression)pParent).getArgumentList())) { + final JavaResolveResult resolveResult = ((PsiCallExpression)pParent).resolveMethodGenerics(); + final PsiElement psiElement = resolveResult.getElement(); + if (psiElement instanceof PsiMethod) { + final PsiMethod psiMethod = (PsiMethod)psiElement; + final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); + final int idx = ArrayUtilRt.find(((PsiExpressionList)parent).getExpressions(), PsiUtil.skipParenthesizedExprUp(myPlace)); + if (idx > -1 && parameters.length > 0) { + PsiType parameterType = parameters[Math.min(idx, parameters.length - 1)].getType(); + if (idx >= parameters.length - 1) { + final PsiParameter lastParameter = parameters[parameters.length - 1]; + if (lastParameter.isVarArgs()) { + parameterType = ((PsiEllipsisType)lastParameter.getType()).getComponentType(); + } + } + return resolveResult.getSubstitutor().substitute(parameterType); + } + else { + return null; + } + } + } + } + else if (parent instanceof PsiLambdaExpression) { + return LambdaUtil.getFunctionalInterfaceReturnType(((PsiLambdaExpression)parent).getFunctionalInterfaceType()); + } + return null; + } + + @Override + public boolean process(T member) { + ProgressManager.checkCanceled(); + if (JavaCompletionUtil.isInExcludedPackage(member, false) || !member.hasModifierProperty(PsiModifier.STATIC)) return true; + PsiFile file = member.getContainingFile(); + final PsiClass containingClass = member.getContainingClass(); + if (file instanceof PsiJavaFile + //do not show methods from default package + && !((PsiJavaFile)file).getPackageName().isEmpty()) { + if (isEffectivelyDeprecated(member)) { + myDeprecated.putValue(containingClass, member); + return processCondition(); + } + mySuggestions.putValue(containingClass, member); + } + return processCondition(); + } + + private boolean isEffectivelyDeprecated(T member) { + if (((PsiDocCommentOwner)member).isDeprecated()) { + return true; + } + + PsiClass aClass = member.getContainingClass(); + while (aClass != null) { + if (aClass.isDeprecated()) { + return true; + } + aClass = aClass.getContainingClass(); + } + return false; + } + + private boolean processCondition() { + return mySuggestions.size() + myDeprecated.size() < 50; + } + + private void registerMember(PsiClass containingClass, + Collection members, + List list, + List applicableList) { + final Boolean alreadyMentioned = myPossibleClasses.get(containingClass); + if (alreadyMentioned == Boolean.TRUE) return; + if (alreadyMentioned == null) { + if (!members.isEmpty()) { + list.add(members.iterator().next()); + } + myPossibleClasses.put(containingClass, false); + } + for (T member : members) { + if (!PsiUtil.isAccessible(myPlace.getProject(), member, myPlace, containingClass)) { + continue; + } + if (isApplicable(member, myPlace)) { + applicableList.add(member); + myPossibleClasses.put(containingClass, true); + break; + } + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportConstantFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportConstantFix.java new file mode 100644 index 000000000000..489596bc50e0 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportConstantFix.java @@ -0,0 +1,102 @@ +/* + * Copyright 2000-2016 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.daemon.impl.quickfix; + +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.*; +import com.intellij.psi.search.PsiShortNamesCache; +import com.intellij.psi.util.PsiFormatUtil; +import com.intellij.psi.util.PsiFormatUtilBase; +import com.intellij.psi.util.TypeConversionUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class StaticImportConstantFix extends StaticImportMemberFix { + private final SmartPsiElementPointer myRef; + + public StaticImportConstantFix(@NotNull PsiJavaCodeReferenceElement referenceElement) { + myRef = SmartPointerManager.getInstance(referenceElement.getProject()).createSmartPsiElementPointer(referenceElement); + } + + @NotNull + @Override + protected String getBaseText() { + return "Static import constant"; + } + + @NotNull + @Override + protected String getMemberPresentableText(PsiField field) { + return PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_NAME | + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | + PsiFormatUtilBase.SHOW_FQ_NAME, PsiSubstitutor.EMPTY); + } + + @NotNull + @Override + protected List getMembersToImport() { + final Project project = myRef.getProject(); + PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project); + final PsiJavaCodeReferenceElement element = myRef.getElement(); + String name = element != null ? element.getReferenceName() : null; + if (name == null) return Collections.emptyList(); + final MyStaticMembersProcessor processor = new MyStaticMembersProcessor(element) { + @Override + protected boolean isApplicable(PsiField field, PsiElement place) { + final PsiType expectedType = getExpectedType(); + return expectedType == null || TypeConversionUtil.isAssignable(expectedType, field.getType()); + } + }; + cache.processFieldsWithName(name, processor, element.getResolveScope(), null); + return processor.getMembersToImport(); + } + + @NotNull + protected StaticImportMethodQuestionAction createQuestionAction(List methodsToImport, @NotNull Project project, Editor editor) { + return new StaticImportMethodQuestionAction(project, editor, methodsToImport, myRef) { + @NotNull + @Override + protected String getPopupTitle() { + return QuickFixBundle.message("field.to.import.chooser.title"); + } + }; + } + + @Nullable + @Override + protected PsiElement getElement() { + return myRef.getElement(); + } + + @Nullable + @Override + protected PsiElement getQualifierExpression() { + final PsiJavaCodeReferenceElement element = myRef.getElement(); + return element != null ? element.getQualifier() : null; + } + + @Nullable + @Override + protected PsiElement resolveRef() { + final PsiJavaCodeReferenceElement referenceElement = (PsiJavaCodeReferenceElement)getElement(); + return referenceElement != null ? referenceElement.resolve() : null; + } +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMemberFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMemberFix.java new file mode 100644 index 000000000000..481417926ab6 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMemberFix.java @@ -0,0 +1,146 @@ +/* + * Copyright 2000-2016 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.daemon.impl.quickfix; + +import com.intellij.codeInsight.FileModificationService; +import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; +import com.intellij.codeInsight.hint.HintManager; +import com.intellij.codeInsight.hint.QuestionAction; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.codeInspection.HintAction; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaFile; +import com.intellij.psi.PsiMember; +import com.intellij.psi.util.PsiUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class StaticImportMemberFix implements IntentionAction, HintAction { + private List candidates; + + @NotNull protected abstract String getBaseText(); + @NotNull protected abstract String getMemberPresentableText(T t); + + @Override + @NotNull + public String getText() { + String text = getBaseText(); + if (candidates != null && candidates.size() == 1) { + text += " '" + getMemberPresentableText(candidates.get(0)) + "'"; + } + else { + text += "..."; + } + return text; + } + + @Override + @NotNull + public String getFamilyName() { + return getText(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + return PsiUtil.isLanguageLevel5OrHigher(file) + && file instanceof PsiJavaFile + && getElement() != null + && getElement().isValid() + && getQualifierExpression() == null + && resolveRef() == null + && file.getManager().isInProject(file) + && !(candidates == null ? candidates = getMembersToImport() : candidates).isEmpty() + ; + } + + @NotNull protected abstract List getMembersToImport(); + @NotNull protected abstract QuestionAction createQuestionAction(List methodsToImport, @NotNull Project project, Editor editor); + + @Nullable protected abstract PsiElement getElement(); + @Nullable protected abstract PsiElement getQualifierExpression(); + @Nullable protected abstract PsiElement resolveRef(); + + @Override + public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) { + if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final List methodsToImport = getMembersToImport(); + if (methodsToImport.isEmpty()) return; + createQuestionAction(methodsToImport, project, editor).execute(); + } + }); + } + + private ImportClassFixBase.Result doFix(Editor editor) { + if (candidates.isEmpty()) { + return ImportClassFixBase.Result.POPUP_NOT_SHOWN; + } + + final PsiElement element = getElement(); + if (element == null) { + return ImportClassFixBase.Result.POPUP_NOT_SHOWN; + } + + final QuestionAction action = createQuestionAction(candidates, element.getProject(), editor); + if (candidates.size() == 1 && ImportClassFixBase.canAddUnambiguousImport(element.getContainingFile())) { + CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { + @Override + public void run() { + action.execute(); + } + }); + return ImportClassFixBase.Result.CLASS_AUTO_IMPORTED; + } + + String hintText = ShowAutoImportPass.getMessage(candidates.size() > 1, getMemberPresentableText(candidates.get(0))); + if (!ApplicationManager.getApplication().isUnitTestMode() && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) { + final TextRange textRange = element.getTextRange(); + HintManager.getInstance().showQuestionHint(editor, hintText, + textRange.getStartOffset(), + textRange.getEndOffset(), action); + } + return ImportClassFixBase.Result.POPUP_SHOWN; + } + + + + @Override + public boolean startInWriteAction() { + return false; + } + + @Override + public boolean showHint(@NotNull Editor editor) { + final PsiElement callExpression = getElement(); + if (callExpression == null || + getQualifierExpression() != null) { + return false; + } + ImportClassFixBase.Result result = doFix(editor); + return result == ImportClassFixBase.Result.POPUP_SHOWN || result == ImportClassFixBase.Result.CLASS_AUTO_IMPORTED; + } + +} diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java index 79e2f69eee20..7a960fdd8188 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodFix.java @@ -15,245 +15,56 @@ */ package com.intellij.codeInsight.daemon.impl.quickfix; -import com.intellij.codeInsight.FileModificationService; import com.intellij.codeInsight.JavaProjectCodeInsightSettings; -import com.intellij.codeInsight.completion.JavaCompletionUtil; import com.intellij.codeInsight.daemon.QuickFixBundle; -import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; -import com.intellij.codeInsight.hint.HintManager; -import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.codeInspection.HintAction; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.DefaultParameterTypeInferencePolicy; -import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiShortNamesCache; -import com.intellij.psi.util.*; -import com.intellij.psi.util.proximity.PsiProximityComparator; -import com.intellij.util.ArrayUtilRt; -import com.intellij.util.Processor; -import com.intellij.util.containers.LinkedMultiMap; -import com.intellij.util.containers.MultiMap; +import com.intellij.psi.util.PsiFormatUtil; +import com.intellij.psi.util.PsiFormatUtilBase; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.TypeConversionUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.Collections; +import java.util.List; -public class StaticImportMethodFix implements IntentionAction, HintAction { - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.quickfix.StaticImportMethodFix"); +public class StaticImportMethodFix extends StaticImportMemberFix { private final SmartPsiElementPointer myMethodCall; - private List candidates; public StaticImportMethodFix(@NotNull PsiMethodCallExpression methodCallExpression) { myMethodCall = SmartPointerManager.getInstance(methodCallExpression.getProject()).createSmartPsiElementPointer(methodCallExpression); } + @NotNull @Override - @NotNull - public String getText() { - String text = QuickFixBundle.message("static.import.method.text"); - if (candidates != null && candidates.size() == 1) { - text += " '" + getMethodPresentableText() + "'"; - } - else { - text += "..."; - } - return text; + protected String getBaseText() { + return QuickFixBundle.message("static.import.method.text"); } @NotNull - private String getMethodPresentableText() { - return PsiFormatUtil.formatMethod(candidates.get(0), PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME | - PsiFormatUtilBase.SHOW_CONTAINING_CLASS | - PsiFormatUtilBase.SHOW_FQ_NAME, 0); - } - @Override - @NotNull - public String getFamilyName() { - return getText(); + protected String getMemberPresentableText(PsiMethod method) { + return PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_NAME | + PsiFormatUtilBase.SHOW_CONTAINING_CLASS | + PsiFormatUtilBase.SHOW_FQ_NAME, 0); } + @NotNull @Override - public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return PsiUtil.isLanguageLevel5OrHigher(file) - && file instanceof PsiJavaFile - && myMethodCall.getElement() != null - && myMethodCall.getElement().isValid() - && myMethodCall.getElement().getMethodExpression().getQualifierExpression() == null - && myMethodCall.getElement().resolveMethod() == null - && file.getManager().isInProject(file) - && !(candidates == null ? candidates = getMethodsToImport() : candidates).isEmpty() - ; - } - - private PsiType getExpectedType() { - final PsiMethodCallExpression methodCall = myMethodCall.getElement(); - if (methodCall == null) return null; - final PsiElement parent = PsiUtil.skipParenthesizedExprUp(methodCall.getParent()); - - if (parent instanceof PsiVariable) { - if (methodCall.equals(PsiUtil.skipParenthesizedExprDown(((PsiVariable)parent).getInitializer()))) { - return ((PsiVariable)parent).getType(); - } - } - else if (parent instanceof PsiAssignmentExpression) { - if (methodCall.equals(PsiUtil.skipParenthesizedExprDown(((PsiAssignmentExpression)parent).getRExpression()))) { - return ((PsiAssignmentExpression)parent).getLExpression().getType(); - } - } - else if (parent instanceof PsiReturnStatement) { - final PsiElement psiElement = PsiTreeUtil.getParentOfType(parent, PsiLambdaExpression.class, PsiMethod.class); - if (psiElement instanceof PsiLambdaExpression) { - return LambdaUtil.getFunctionalInterfaceReturnType(((PsiLambdaExpression)psiElement).getFunctionalInterfaceType()); - } - else if (psiElement instanceof PsiMethod) { - return ((PsiMethod)psiElement).getReturnType(); - } - } - else if (parent instanceof PsiExpressionList) { - final PsiElement pParent = parent.getParent(); - if (pParent instanceof PsiCallExpression && parent.equals(((PsiCallExpression)pParent).getArgumentList())) { - final JavaResolveResult resolveResult = ((PsiCallExpression)pParent).resolveMethodGenerics(); - final PsiElement psiElement = resolveResult.getElement(); - if (psiElement instanceof PsiMethod) { - final PsiMethod psiMethod = (PsiMethod)psiElement; - final PsiParameter[] parameters = psiMethod.getParameterList().getParameters(); - final int idx = ArrayUtilRt.find(((PsiExpressionList)parent).getExpressions(), PsiUtil.skipParenthesizedExprUp(methodCall)); - if (idx > -1 && parameters.length > 0) { - PsiType parameterType = parameters[Math.min(idx, parameters.length - 1)].getType(); - if (idx >= parameters.length - 1) { - final PsiParameter lastParameter = parameters[parameters.length - 1]; - if (lastParameter.isVarArgs()) { - parameterType = ((PsiEllipsisType)lastParameter.getType()).getComponentType(); - } - } - return resolveResult.getSubstitutor().substitute(parameterType); - } - else { - return null; - } - } - } - } - else if (parent instanceof PsiLambdaExpression) { - return LambdaUtil.getFunctionalInterfaceReturnType(((PsiLambdaExpression)parent).getFunctionalInterfaceType()); - } - return null; - } - - @NotNull - private List getMethodsToImport() { + protected List getMembersToImport() { final Project project = myMethodCall.getProject(); PsiShortNamesCache cache = PsiShortNamesCache.getInstance(project); final PsiMethodCallExpression element = myMethodCall.getElement(); - PsiReferenceExpression reference = element.getMethodExpression(); - final PsiExpressionList argumentList = element.getArgumentList(); - String name = reference.getReferenceName(); - final List list = new ArrayList(); - if (name == null) return list; - GlobalSearchScope scope = element.getResolveScope(); - final Map possibleClasses = new HashMap(); - final PsiType expectedType = getExpectedType(); - final List applicableList = new ArrayList(); - final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(project).getResolveHelper(); - - final MultiMap deprecated = new LinkedMultiMap(); - final MultiMap suggestions = new LinkedMultiMap(); - class RegisterMethodsProcessor { - private void registerMethod(PsiClass containingClass, Collection methods) { - final Boolean alreadyMentioned = possibleClasses.get(containingClass); - if (alreadyMentioned == Boolean.TRUE) return; - if (alreadyMentioned == null) { - if (!methods.isEmpty()) { - list.add(methods.iterator().next()); - } - possibleClasses.put(containingClass, false); - } - for (PsiMethod method : methods) { - if (!PsiUtil.isAccessible(project, method, element, containingClass)) { - continue; - } - PsiSubstitutor substitutorForMethod = resolveHelper - .inferTypeArguments(method.getTypeParameters(), method.getParameterList().getParameters(), - argumentList.getExpressions(), - PsiSubstitutor.EMPTY, element.getParent(), DefaultParameterTypeInferencePolicy.INSTANCE); - if (PsiUtil.isApplicable(method, substitutorForMethod, argumentList)) { - final PsiType returnType = substitutorForMethod.substitute(method.getReturnType()); - if (expectedType == null || returnType == null || TypeConversionUtil.isAssignable(expectedType, returnType)) { - applicableList.add(method); - possibleClasses.put(containingClass, true); - break; - } - } - } - } - } - - final RegisterMethodsProcessor registrar = new RegisterMethodsProcessor(); - cache.processMethodsWithName(name, scope, new Processor() { - @Override - public boolean process(PsiMethod method) { - ProgressManager.checkCanceled(); - if (JavaCompletionUtil.isInExcludedPackage(method, false) - || !method.hasModifierProperty(PsiModifier.STATIC)) return true; - PsiFile file = method.getContainingFile(); - final PsiClass containingClass = method.getContainingClass(); - if (file instanceof PsiJavaFile - //do not show methods from default package - && !((PsiJavaFile)file).getPackageName().isEmpty()) { - if (isEffectivelyDeprecated(method)) { - deprecated.putValue(containingClass, method); - return processCondition(); - } - suggestions.putValue(containingClass, method); - } - return processCondition(); - } - - private boolean isEffectivelyDeprecated(PsiMethod method) { - if (method.isDeprecated()) { - return true; - } - PsiClass aClass = method.getContainingClass(); - while (aClass != null) { - if (aClass.isDeprecated()) { - return true; - } - aClass = aClass.getContainingClass(); - } - return false; - } - - private boolean processCondition() { - return suggestions.size() + deprecated.size() < 50; - } - }); - - for (Map.Entry> methodEntry : suggestions.entrySet()) { - registrar.registerMethod(methodEntry.getKey(), methodEntry.getValue()); - } - - for (Map.Entry> deprecatedMethod : deprecated.entrySet()) { - registrar.registerMethod(deprecatedMethod.getKey(), deprecatedMethod.getValue()); - } - - List result = applicableList.isEmpty() ? list : applicableList; - for (int i = result.size() - 1; i >= 0; i--) { - ProgressManager.checkCanceled(); - PsiMethod method = result.get(i); - // check for manually excluded - if (isExcluded(method)) { - result.remove(i); - } - } - Collections.sort(result, new PsiProximityComparator(argumentList)); - return result; + PsiReferenceExpression reference = element == null ? null : element.getMethodExpression(); + String name = reference == null ? null : reference.getReferenceName(); + if (name == null) return Collections.emptyList(); + final MyStaticMembersProcessor processor = new MyStaticMethodProcessor(project, element); + cache.processMethodsWithName(name, element.getResolveScope(), processor); + return processor.getMembersToImport(); } public static boolean isExcluded(PsiMember method) { @@ -261,68 +72,51 @@ public class StaticImportMethodFix implements IntentionAction, HintAction { return name != null && JavaProjectCodeInsightSettings.getSettings(method.getProject()).isExcluded(name); } - @Override - public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) { - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - final List methodsToImport = getMethodsToImport(); - if (methodsToImport.isEmpty()) return; - createQuestionAction(methodsToImport, project, editor).execute(); - } - }); - } - @NotNull - private StaticImportMethodQuestionAction createQuestionAction(List methodsToImport, @NotNull Project project, Editor editor) { - return new StaticImportMethodQuestionAction(project, editor, methodsToImport, myMethodCall); + protected StaticImportMethodQuestionAction createQuestionAction(List methodsToImport, @NotNull Project project, Editor editor) { + return new StaticImportMethodQuestionAction(project, editor, methodsToImport, myMethodCall); } - private ImportClassFixBase.Result doFix(Editor editor) { - if (candidates.isEmpty()) { - return ImportClassFixBase.Result.POPUP_NOT_SHOWN; - } - - final StaticImportMethodQuestionAction action = createQuestionAction(candidates, myMethodCall.getProject(), editor); + @Nullable + @Override + protected PsiElement getElement() { + return myMethodCall.getElement(); + } + @Nullable + @Override + protected PsiElement getQualifierExpression() { final PsiMethodCallExpression element = myMethodCall.getElement(); - if (element == null) { - return ImportClassFixBase.Result.POPUP_NOT_SHOWN; - } - - if (candidates.size() == 1 && ImportClassFixBase.canAddUnambiguousImport(element.getContainingFile())) { - CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() { - @Override - public void run() { - action.execute(); - } - }); - return ImportClassFixBase.Result.CLASS_AUTO_IMPORTED; - } - - String hintText = ShowAutoImportPass.getMessage(candidates.size() > 1, getMethodPresentableText()); - if (!ApplicationManager.getApplication().isUnitTestMode() && !HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) { - final TextRange textRange = element.getTextRange(); - HintManager.getInstance().showQuestionHint(editor, hintText, - textRange.getStartOffset(), - textRange.getEndOffset(), action); - } - return ImportClassFixBase.Result.POPUP_SHOWN; + return element != null ? element.getMethodExpression().getQualifierExpression() : null; } + @Nullable @Override - public boolean startInWriteAction() { - return false; + protected PsiElement resolveRef() { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)getElement(); + return methodCallExpression != null ? methodCallExpression.resolveMethod() : null; } - @Override - public boolean showHint(@NotNull Editor editor) { - final PsiMethodCallExpression callExpression = myMethodCall.getElement(); - if (callExpression == null || callExpression.getMethodExpression().getQualifierExpression() != null) { + private static class MyStaticMethodProcessor extends MyStaticMembersProcessor { + + private MyStaticMethodProcessor(Project project, PsiMethodCallExpression place) { + super(place); + } + + @Override + protected boolean isApplicable(PsiMethod method, PsiElement place) { + final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(place.getProject()).getResolveHelper(); + final PsiExpressionList argumentList = ((PsiMethodCallExpression)place).getArgumentList(); + PsiSubstitutor substitutorForMethod = resolveHelper + .inferTypeArguments(method.getTypeParameters(), method.getParameterList().getParameters(), + argumentList.getExpressions(), + PsiSubstitutor.EMPTY, place.getParent(), DefaultParameterTypeInferencePolicy.INSTANCE); + if (PsiUtil.isApplicable(method, substitutorForMethod, argumentList)) { + final PsiType returnType = substitutorForMethod.substitute(method.getReturnType()); + final PsiType expectedType = getExpectedType(); + return expectedType == null || returnType == null || TypeConversionUtil.isAssignable(expectedType, returnType); + } return false; } - ImportClassFixBase.Result result = doFix(editor); - return result == ImportClassFixBase.Result.POPUP_SHOWN || result == ImportClassFixBase.Result.CLASS_AUTO_IMPORTED; } } diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java index 4f83e31e79ed..bfd9e4c52dcd 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/StaticImportMethodQuestionAction.java @@ -19,7 +19,8 @@ import com.intellij.codeInsight.daemon.QuickFixBundle; import com.intellij.codeInsight.daemon.impl.actions.AddImportAction; import com.intellij.codeInsight.hint.QuestionAction; import com.intellij.codeInsight.intention.impl.AddSingleMemberStaticImportAction; -import com.intellij.ide.util.MethodCellRenderer; +import com.intellij.ide.util.PsiClassListCellRenderer; +import com.intellij.ide.util.PsiElementListCellRenderer; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; @@ -29,7 +30,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.PopupStep; import com.intellij.openapi.ui.popup.util.BaseListPopupStep; import com.intellij.psi.*; -import com.intellij.psi.util.PsiFormatUtilBase; +import com.intellij.psi.presentation.java.ClassPresentationUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.ui.popup.list.ListPopupImpl; import com.intellij.ui.popup.list.PopupListElementRenderer; @@ -42,33 +43,38 @@ import javax.swing.*; import java.awt.*; import java.util.List; -public class StaticImportMethodQuestionAction implements QuestionAction { +public class StaticImportMethodQuestionAction implements QuestionAction { private static final Logger LOG = Logger.getInstance("#" + StaticImportMethodQuestionAction.class.getName()); private final Project myProject; private final Editor myEditor; - private List myCandidates; - private final SmartPsiElementPointer myMethodCall; + private List myCandidates; + private final SmartPsiElementPointer myRef; public StaticImportMethodQuestionAction(Project project, Editor editor, - List candidates, - SmartPsiElementPointer methodCall) { + List candidates, + SmartPsiElementPointer ref) { myProject = project; myEditor = editor; myCandidates = candidates; - myMethodCall = methodCall; + myRef = ref; + } + + @NotNull + protected String getPopupTitle() { + return QuickFixBundle.message("method.to.import.chooser.title"); } @Override public boolean execute() { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - final PsiMethodCallExpression element = myMethodCall.getElement(); + final PsiElement element = myRef.getElement(); if (element == null || !element.isValid()){ return false; } - for (PsiMethod targetMethod : myCandidates) { + for (T targetMethod : myCandidates) { if (!targetMethod.isValid()) { return false; } @@ -83,7 +89,7 @@ public class StaticImportMethodQuestionAction implements QuestionAction { return true; } - private void doImport(final PsiMethod toImport) { + private void doImport(final T toImport) { final Project project = toImport.getProject(); CommandProcessor.getInstance().executeCommand(project, new Runnable(){ @Override @@ -92,7 +98,7 @@ public class StaticImportMethodQuestionAction implements QuestionAction { @Override public void run() { try { - PsiMethodCallExpression element = myMethodCall.getElement(); + PsiElement element = myRef.getElement(); if (element != null) { AddSingleMemberStaticImportAction.bindAllClassRefs(element.getContainingFile(), toImport, toImport.getName(), toImport.getContainingClass()); } @@ -113,11 +119,11 @@ public class StaticImportMethodQuestionAction implements QuestionAction { doImport(myCandidates.get(0)); return; } - final BaseListPopupStep step = - new BaseListPopupStep(QuickFixBundle.message("method.to.import.chooser.title"), myCandidates) { + final BaseListPopupStep step = + new BaseListPopupStep(getPopupTitle(), myCandidates) { @Override - public PopupStep onChosen(PsiMethod selectedValue, boolean finalChoice) { + public PopupStep onChosen(T selectedValue, boolean finalChoice) { if (selectedValue == null) { return FINAL_CHOICE; } @@ -151,18 +157,18 @@ public class StaticImportMethodQuestionAction implements QuestionAction { } @Override - public boolean hasSubstep(PsiMethod selectedValue) { + public boolean hasSubstep(T selectedValue) { return true; } @NotNull @Override - public String getTextFor(PsiMethod value) { + public String getTextFor(T value) { return ObjectUtils.assertNotNull(value.getName()); } @Override - public Icon getIconFor(PsiMethod aValue) { + public Icon getIconFor(T aValue) { return aValue.getIcon(0); } }; @@ -171,14 +177,27 @@ public class StaticImportMethodQuestionAction implements QuestionAction { final PopupListElementRenderer rightArrow = new PopupListElementRenderer(this); @Override protected ListCellRenderer getListElementRenderer() { - return new MethodCellRenderer(true, PsiFormatUtilBase.SHOW_NAME){ + return new PsiElementListCellRenderer() { + public String getElementText(T element) { + final PsiClass aClass = element.getContainingClass(); + LOG.assertTrue(aClass != null); + return ClassPresentationUtil.getNameForClass(aClass, false) + "." + element.getName(); + } + + public String getContainerText(final T element, final String name) { + return PsiClassListCellRenderer.getContainerTextStatic(element); + } + + public int getIconFlags() { + return 0; + } @Nullable @Override protected TextAttributes getNavigationItemAttributes(Object value) { TextAttributes attrs = super.getNavigationItemAttributes(value); - if (value instanceof PsiMethod && !((PsiMethod)value).isDeprecated()) { - PsiClass psiClass = ((PsiMethod)value).getContainingClass(); + if (value instanceof PsiDocCommentOwner && !((PsiDocCommentOwner)value).isDeprecated()) { + PsiClass psiClass = ((T)value).getContainingClass(); if (psiClass != null && psiClass.isDeprecated()) { return TextAttributes.merge(attrs, super.getNavigationItemAttributes(psiClass)); } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/afterConstant.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/afterConstant.java new file mode 100644 index 000000000000..00620ce2ed20 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/afterConstant.java @@ -0,0 +1,17 @@ +// "Static import constant..." "true" +package foo; + +import static foo.B.aaaaaaa; + +public class X { + { + aaaaaaa; + } +} + +class B { + public static Integer aaaaaaa = 1; +} +class B1 { + public static String aaaaaaa = ""; +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/afterConstantInAssignment.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/afterConstantInAssignment.java new file mode 100644 index 000000000000..9ffd4f90553f --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/afterConstantInAssignment.java @@ -0,0 +1,17 @@ +// "Static import constant 'foo.B.aaaaaaa'" "true" +package foo; + +import static foo.B.aaaaaaa; + +public class X { + { + int i = aaaaaaa; + } +} + +class B { + public static Integer aaaaaaa = 1; +} +class B1 { + public static String aaaaaaa = ""; +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/beforeConstant.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/beforeConstant.java new file mode 100644 index 000000000000..aa0b01907d4a --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/beforeConstant.java @@ -0,0 +1,15 @@ +// "Static import constant..." "true" +package foo; + +public class X { + { + aaaaaaa; + } +} + +class B { + public static Integer aaaaaaa = 1; +} +class B1 { + public static String aaaaaaa = ""; +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/beforeConstantInAssignment.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/beforeConstantInAssignment.java new file mode 100644 index 000000000000..5781759480f9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant/beforeConstantInAssignment.java @@ -0,0 +1,15 @@ +// "Static import constant 'foo.B.aaaaaaa'" "true" +package foo; + +public class X { + { + int i = aaaaaaa; + } +} + +class B { + public static Integer aaaaaaa = 1; +} +class B1 { + public static String aaaaaaa = ""; +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/StaticImportConstantTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/StaticImportConstantTest.java new file mode 100644 index 000000000000..decfb04dca89 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/quickFix/StaticImportConstantTest.java @@ -0,0 +1,23 @@ +/* + * Copyright 2000-2016 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.daemon.quickFix; + +public class StaticImportConstantTest extends LightQuickFixParameterizedTestCase { + @Override + protected String getBasePath() { + return "/codeInsight/daemonCodeAnalyzer/quickFix/staticImportConstant"; + } +} diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index 506d63d4b51f..0b853daccd46 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -1,6 +1,7 @@ add.import=Add Import class.to.import.chooser.title=Class to Import method.to.import.chooser.title=Method to Import +field.to.import.chooser.title=Field to Import access.static.via.class.reference.family=Access static via class reference access.static.via.class.reference.text=Access static ''{1}.{0}'' via class ''{2}'' reference add.default.constructor.family=Add Default Constructor