diff --git a/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java b/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java index d038dbd53..422c2d5ce 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java +++ b/plugin/src/main/java/com/intellij/java/impl/ig/classlayout/ProtectedMemberInFinalClassInspection.java @@ -20,7 +20,6 @@ import com.intellij.java.language.impl.psi.impl.source.resolve.JavaResolveUtil; import com.intellij.java.language.psi.*; import com.intellij.java.language.psi.search.searches.SuperMethodsSearch; -import com.intellij.java.language.psi.util.MethodSignatureBackedByPsiMethod; import com.siyeh.ig.BaseInspection; import com.siyeh.ig.BaseInspectionVisitor; import com.siyeh.ig.InspectionGadgetsFix; @@ -29,7 +28,6 @@ import consulo.annotation.component.ExtensionImpl; import consulo.application.AccessToken; import consulo.application.WriteAction; -import consulo.application.util.function.Processor; import consulo.application.util.query.Query; import consulo.language.editor.inspection.ProblemDescriptor; import consulo.language.editor.refactoring.localize.RefactoringLocalize; @@ -94,59 +92,49 @@ public void doFix(Project project, ProblemDescriptor descriptor) throws Incorrec if (modifierList == null) { return; } - final MultiMap conflicts = new MultiMap(); + final MultiMap conflicts = new MultiMap<>(); if (member instanceof PsiMethod) { final PsiMethod method = (PsiMethod) member; - SuperMethodsSearch.search(method, method.getContainingClass(), true, false).forEach( - new Processor() { - @Override - public boolean process(MethodSignatureBackedByPsiMethod methodSignature) { - final PsiMethod superMethod = methodSignature.getMethod(); - conflicts.putValue( - superMethod, - InspectionGadgetsLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithSuper1( - RefactoringUIUtil.getDescription(method, false), - RefactoringUIUtil.getDescription(superMethod, true) - ).get() - ); - return true; - } - }); - OverridingMethodsSearch.search(method).forEach(new Processor() { - @Override - public boolean process(PsiMethod overridingMethod) { - conflicts.putValue( - overridingMethod, - InspectionGadgetsLocalize.zeroWillNoLongerBeVisibleFromOverriding1( - RefactoringUIUtil.getDescription(method, false), - RefactoringUIUtil.getDescription(overridingMethod, true) - ).get() - ); - return false; - } + SuperMethodsSearch.search(method, method.getContainingClass(), true, false).forEach(methodSignature -> { + PsiMethod superMethod = methodSignature.getMethod(); + conflicts.putValue( + superMethod, + InspectionGadgetsLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithSuper1( + RefactoringUIUtil.getDescription(method, false), + RefactoringUIUtil.getDescription(superMethod, true) + ) + ); + return true; + }); + OverridingMethodsSearch.search(method).forEach(overridingMethod -> { + conflicts.putValue( + overridingMethod, + InspectionGadgetsLocalize.zeroWillNoLongerBeVisibleFromOverriding1( + RefactoringUIUtil.getDescription(method, false), + RefactoringUIUtil.getDescription(overridingMethod, true) + ) + ); + return false; }); } final PsiModifierList modifierListCopy = (PsiModifierList) modifierList.copy(); modifierListCopy.setModifierProperty(PsiModifier.PRIVATE, true); final Query search = ReferencesSearch.search(member, member.getResolveScope()); - search.forEach(new Processor() { - @Override - public boolean process(PsiReference reference) { - final PsiElement element = reference.getElement(); - if (!JavaResolveUtil.isAccessible(member, member.getContainingClass(), modifierListCopy, element, null, null)) { - final PsiElement context = - PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiField.class, PsiClass.class, PsiFile.class); - conflicts.putValue( - element, - RefactoringLocalize.zeroWith1VisibilityIsNotAccessibleFrom2( - RefactoringUIUtil.getDescription(member, false), - PsiBundle.visibilityPresentation(PsiModifier.PRIVATE), - RefactoringUIUtil.getDescription(context, true) - ).get() - ); - } - return true; + search.forEach(reference -> { + PsiElement element1 = reference.getElement(); + if (!JavaResolveUtil.isAccessible(member, member.getContainingClass(), modifierListCopy, element1, null, null)) { + PsiElement context = + PsiTreeUtil.getParentOfType(element1, PsiMethod.class, PsiField.class, PsiClass.class, PsiFile.class); + conflicts.putValue( + element1, + RefactoringLocalize.zeroWith1VisibilityIsNotAccessibleFrom2( + RefactoringUIUtil.getDescription(member, false), + PsiBundle.visibilityPresentation(PsiModifier.PRIVATE), + RefactoringUIUtil.getDescription(context, true) + ) + ); } + return true; }); final boolean conflictsDialogOK; if (conflicts.isEmpty()) { @@ -156,10 +144,11 @@ public boolean process(PsiReference reference) { if (!isOnTheFly()) { return; } - final ConflictsDialog conflictsDialog = new ConflictsDialog(member.getProject(), conflicts, new Runnable() { - @Override - public void run() { - final AccessToken token = WriteAction.start(); + ConflictsDialog conflictsDialog = new ConflictsDialog( + member.getProject(), + conflicts, + (Runnable) () -> { + AccessToken token = WriteAction.start(); try { modifierList.setModifierProperty(PsiModifier.PRIVATE, true); } @@ -167,7 +156,7 @@ public void run() { token.finish(); } } - }); + ); conflictsDialog.show(); conflictsDialogOK = conflictsDialog.isOK(); } diff --git a/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java b/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java index 21b74162d..394b3ccb4 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java +++ b/plugin/src/main/java/com/intellij/java/impl/ipp/interfacetoclass/ConvertInterfaceToClassIntention.java @@ -113,7 +113,7 @@ protected void processIntention(@Nonnull PsiElement element) throws IncorrectOpe final PsiClass anInterface = (PsiClass) element.getParent(); final SearchScope searchScope = anInterface.getUseScope(); final Query query = ClassInheritorsSearch.search(anInterface, searchScope, false); - final MultiMap conflicts = new MultiMap(); + final MultiMap conflicts = new MultiMap<>(); query.forEach(new Processor() { @Override public boolean process(PsiClass aClass) { @@ -125,7 +125,14 @@ public boolean process(PsiClass aClass) { if (referenceElements.length > 0) { final PsiElement target = referenceElements[0].resolve(); if (target != null) { - conflicts.putValue(aClass, IntentionPowerPackLocalize.zeroAlreadyExtends1AndWillNotCompileAfterConverting2ToAClass(RefactoringUIUtil.getDescription(aClass, true), RefactoringUIUtil.getDescription(target, true), RefactoringUIUtil.getDescription(anInterface, false)).get()); + conflicts.putValue( + aClass, + IntentionPowerPackLocalize.zeroAlreadyExtends1AndWillNotCompileAfterConverting2ToAClass( + RefactoringUIUtil.getDescription(aClass, true), + RefactoringUIUtil.getDescription(target, true), + RefactoringUIUtil.getDescription(anInterface, false) + ) + ); } } return true; @@ -136,10 +143,11 @@ public boolean process(PsiClass aClass) { conflictsDialogOK = true; } else { - final ConflictsDialog conflictsDialog = new ConflictsDialog(anInterface.getProject(), conflicts, new Runnable() { - @Override - public void run() { - final AccessToken token = WriteAction.start(); + ConflictsDialog conflictsDialog = new ConflictsDialog( + anInterface.getProject(), + conflicts, + () -> { + AccessToken token = WriteAction.start(); try { convertInterfaceToClass(anInterface); } @@ -147,7 +155,7 @@ public void run() { token.finish(); } } - }); + ); conflictsDialog.show(); conflictsDialogOK = conflictsDialog.isOK(); } @@ -205,8 +213,10 @@ private static boolean moveSubClassImplementsToExtends(PsiClass oldInterface) th return true; } - private static void moveReference(@Nonnull PsiReferenceList source, @Nullable PsiReferenceList target, - @Nonnull PsiJavaCodeReferenceElement reference) throws IncorrectOperationException { + private static void moveReference( + @Nonnull PsiReferenceList source, @Nullable PsiReferenceList target, + @Nonnull PsiJavaCodeReferenceElement reference + ) throws IncorrectOperationException { final PsiJavaCodeReferenceElement[] implementsReferences = source.getReferenceElements(); final String qualifiedName = reference.getQualifiedName(); for (PsiJavaCodeReferenceElement implementsReference : implementsReferences) { diff --git a/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java b/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java index 4e365dba5..7fac0b4b1 100644 --- a/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java +++ b/plugin/src/main/java/com/intellij/java/impl/ipp/modifiers/ModifierIntention.java @@ -36,6 +36,7 @@ import consulo.language.psi.search.ReferencesSearch; import consulo.language.psi.util.PsiTreeUtil; import consulo.language.util.IncorrectOperationException; +import consulo.localize.LocalizeValue; import consulo.util.collection.MultiMap; import consulo.util.io.FileUtil; import jakarta.annotation.Nonnull; @@ -64,15 +65,17 @@ protected final void processIntention(@Nonnull PsiElement element) throws Incorr if (modifierList == null) { return; } - final MultiMap conflicts = checkForConflicts(member); + MultiMap conflicts = checkForConflicts(member); final boolean conflictsDialogOK; if (conflicts.isEmpty()) { conflictsDialogOK = true; } else { - final ConflictsDialog conflictsDialog = new ConflictsDialog(member.getProject(), conflicts, () -> { - WriteAction.run(() -> modifierList.setModifierProperty(getModifier(), true)); - }); + ConflictsDialog conflictsDialog = new ConflictsDialog( + member.getProject(), + conflicts, + () -> WriteAction.run(() -> modifierList.setModifierProperty(getModifier(), true)) + ); conflictsDialog.show(); conflictsDialogOK = conflictsDialog.isOK(); } @@ -82,7 +85,7 @@ protected final void processIntention(@Nonnull PsiElement element) throws Incorr } } - private MultiMap checkForConflicts(@Nonnull final PsiMember member) { + private MultiMap checkForConflicts(@Nonnull PsiMember member) { if (member instanceof PsiClass && getModifier().equals(PsiModifier.PUBLIC)) { final PsiClass aClass = (PsiClass) member; final PsiElement parent = aClass.getParent(); @@ -95,30 +98,55 @@ private MultiMap checkForConflicts(@Nonnull final PsiMember if (name.equals(className)) { return MultiMap.empty(); } - final MultiMap conflicts = new MultiMap(); - conflicts.putValue(aClass, IntentionPowerPackLocalize.zeroIsDeclaredIn1ButWhenPublicShouldBeDeclaredInAFileNamed2(RefactoringUIUtil.getDescription(aClass, false), RefactoringUIUtil.getDescription(javaFile, false), CommonRefactoringUtil.htmlEmphasize(className + ".java")).get()); + MultiMap conflicts = new MultiMap<>(); + conflicts.putValue( + aClass, + IntentionPowerPackLocalize.zeroIsDeclaredIn1ButWhenPublicShouldBeDeclaredInAFileNamed2( + RefactoringUIUtil.getDescription(aClass, false), + RefactoringUIUtil.getDescription(javaFile, false), + CommonRefactoringUtil.htmlEmphasize(className + ".java") + ) + ); return conflicts; } final PsiModifierList modifierList = member.getModifierList(); if (modifierList == null || modifierList.hasModifierProperty(PsiModifier.PRIVATE)) { return MultiMap.empty(); } - final MultiMap conflicts = new MultiMap(); + MultiMap conflicts = new MultiMap<>(); if (member instanceof PsiMethod) { final PsiMethod method = (PsiMethod) member; SuperMethodsSearch.search(method, method.getContainingClass(), true, false).forEach(methodSignature -> { final PsiMethod superMethod = methodSignature.getMethod(); if (!hasCompatibleVisibility(superMethod, true)) { - conflicts.putValue(superMethod, IntentionPowerPackLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithSuper1(RefactoringUIUtil.getDescription(method, false), RefactoringUIUtil.getDescription(superMethod, true)).get()); + conflicts.putValue( + superMethod, + IntentionPowerPackLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithSuper1( + RefactoringUIUtil.getDescription(method, false), + RefactoringUIUtil.getDescription(superMethod, true) + ) + ); } return true; }); OverridingMethodsSearch.search(method).forEach(overridingMethod -> { if (!isVisibleFromOverridingMethod(method, overridingMethod)) { - conflicts.putValue(overridingMethod, IntentionPowerPackLocalize.zeroWillNoLongerBeVisibleFromOverriding1(RefactoringUIUtil.getDescription(method, false), RefactoringUIUtil.getDescription(overridingMethod, true)).get()); + conflicts.putValue( + overridingMethod, + IntentionPowerPackLocalize.zeroWillNoLongerBeVisibleFromOverriding1( + RefactoringUIUtil.getDescription(method, false), + RefactoringUIUtil.getDescription(overridingMethod, true) + ) + ); } else if (!hasCompatibleVisibility(overridingMethod, false)) { - conflicts.putValue(overridingMethod, IntentionPowerPackLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithOverriding1(RefactoringUIUtil.getDescription(method, false), RefactoringUIUtil.getDescription(overridingMethod, true)).get()); + conflicts.putValue( + overridingMethod, + IntentionPowerPackLocalize.zeroWillHaveIncompatibleAccessPrivilegesWithOverriding1( + RefactoringUIUtil.getDescription(method, false), + RefactoringUIUtil.getDescription(overridingMethod, true) + ) + ); } return false; }); @@ -141,7 +169,7 @@ else if (!hasCompatibleVisibility(overridingMethod, false)) { RefactoringUIUtil.getDescription(member, false), PsiBundle.visibilityPresentation(getModifier()), RefactoringUIUtil.getDescription(context, true) - ).get() + ) ); return true; }); diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java index 37998fbe7..11aba62b4 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/changeSignature/JavaChangeSignatureUsageProcessor.java @@ -97,7 +97,7 @@ public UsageInfo[] findUsages(@Nonnull ChangeInfo info) { @Nonnull @Override @RequiredReadAction - public MultiMap findConflicts(@Nonnull ChangeInfo info, SimpleReference refUsages) { + public MultiMap findConflicts(@Nonnull ChangeInfo info, SimpleReference refUsages) { if (info instanceof JavaChangeInfo javaChangeInfo) { return new ConflictSearcher(javaChangeInfo).findConflicts(refUsages); } @@ -154,10 +154,10 @@ else if (usage instanceof OverriderUsageInfo info) { boolean toPropagate = javaChangeInfo instanceof JavaChangeInfoImpl changeInfoImpl && changeInfoImpl.propagateParametersMethods.remove(constructor); PsiClass containingClass = defConstructorUsage.getContainingClass(); - constructor = (PsiMethod)containingClass.add(constructor); + constructor = (PsiMethod) containingClass.add(constructor); PsiUtil.setModifierProperty(constructor, VisibilityUtil.getVisibilityModifier(containingClass.getModifierList()), true); if (toPropagate) { - ((JavaChangeInfoImpl)javaChangeInfo).propagateParametersMethods.add(constructor); + ((JavaChangeInfoImpl) javaChangeInfo).propagateParametersMethods.add(constructor); } } addSuperCall(javaChangeInfo, constructor, defConstructorUsage.getBaseConstructor(), usages); @@ -182,7 +182,7 @@ else if (usage instanceof MethodCallUsageInfo methodCallInfo) { else if (usage instanceof ChangeSignatureParameterUsageInfo parameterUsageInfo) { String newName = parameterUsageInfo.newParameterName; String oldName = parameterUsageInfo.oldParameterName; - processParameterUsage((PsiReferenceExpression)element, oldName, newName); + processParameterUsage((PsiReferenceExpression) element, oldName, newName); return true; } else if (usage instanceof CallReferenceUsageInfo callUsageInfo) { @@ -233,8 +233,8 @@ private static void addDefaultConstructor( if (!(aClass instanceof PsiAnonymousClass)) { PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject()); PsiMethod defaultConstructor = factory.createMethodFromText(aClass.getName() + "(){}", aClass); - defaultConstructor = (PsiMethod)CodeStyleManager.getInstance(aClass.getProject()).reformat(defaultConstructor); - defaultConstructor = (PsiMethod)aClass.add(defaultConstructor); + defaultConstructor = (PsiMethod) CodeStyleManager.getInstance(aClass.getProject()).reformat(defaultConstructor); + defaultConstructor = (PsiMethod) aClass.add(defaultConstructor); PsiUtil.setModifierProperty(defaultConstructor, VisibilityUtil.getVisibilityModifier(aClass.getModifierList()), true); addSuperCall(changeInfo, defaultConstructor, null, usages); } @@ -253,17 +253,17 @@ private static void addSuperCall( UsageInfo[] usages ) throws IncorrectOperationException { PsiElementFactory factory = JavaPsiFacade.getElementFactory(constructor.getProject()); - PsiExpressionStatement superCall = (PsiExpressionStatement)factory.createStatementFromText("super();", constructor); + PsiExpressionStatement superCall = (PsiExpressionStatement) factory.createStatementFromText("super();", constructor); PsiCodeBlock body = constructor.getBody(); assert body != null; PsiStatement[] statements = body.getStatements(); if (statements.length > 0) { - superCall = (PsiExpressionStatement)body.addBefore(superCall, statements[0]); + superCall = (PsiExpressionStatement) body.addBefore(superCall, statements[0]); } else { - superCall = (PsiExpressionStatement)body.add(superCall); + superCall = (PsiExpressionStatement) body.add(superCall); } - PsiMethodCallExpression callExpression = (PsiMethodCallExpression)superCall.getExpression(); + PsiMethodCallExpression callExpression = (PsiMethodCallExpression) superCall.getExpression(); PsiClass aClass = constructor.getContainingClass(); PsiClass baseClass = changeInfo.getMethod().getContainingClass(); PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(baseClass, aClass, PsiSubstitutor.EMPTY); @@ -344,11 +344,11 @@ private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) if (!(caughtType instanceof PsiClassType)) { continue; } - if (ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)caughtType)) { + if (ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType) caughtType)) { continue; } - if (!isCatchParameterRedundant((PsiClassType)caughtType, classes)) { + if (!isCatchParameterRedundant((PsiClassType) caughtType, classes)) { continue; } parameter.getParent().delete(); //delete catch section @@ -365,11 +365,11 @@ private static void fixExceptions(PsiElement ref, PsiClassType[] newExceptions) //Add new try statement PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(ref.getProject()); PsiTryStatement tryStatement = - (PsiTryStatement)elementFactory.createStatementFromText("try {} catch (Exception e) {}", null); + (PsiTryStatement) elementFactory.createStatementFromText("try {} catch (Exception e) {}", null); PsiStatement anchor = PsiTreeUtil.getParentOfType(ref, PsiStatement.class); LOG.assertTrue(anchor != null); tryStatement.getTryBlock().add(anchor); - tryStatement = (PsiTryStatement)anchor.getParent().addAfter(tryStatement, anchor); + tryStatement = (PsiTryStatement) anchor.getParent().addAfter(tryStatement, anchor); addExceptions(newExceptions, tryStatement); anchor.delete(); @@ -656,9 +656,9 @@ public boolean processPrimaryMethod(ChangeInfo changeInfo) { PsiElement element = changeInfo.getMethod(); LOG.assertTrue(element instanceof PsiMethod); if (changeInfo.isGenerateDelegate()) { - generateDelegate((JavaChangeInfo)changeInfo); + generateDelegate((JavaChangeInfo) changeInfo); } - processPrimaryMethod((JavaChangeInfo)changeInfo, (PsiMethod)element, null, true); + processPrimaryMethod((JavaChangeInfo) changeInfo, (PsiMethod) element, null, true); return true; } @@ -687,9 +687,9 @@ public boolean setupDefaultValues(ChangeInfo changeInfo, SimpleReference findConflicts(SimpleReference refUsages) { - MultiMap conflictDescriptions = new MultiMap<>(); + public MultiMap findConflicts(SimpleReference refUsages) { + MultiMap conflictDescriptions = new MultiMap<>(); addMethodConflicts(conflictDescriptions); Set usagesSet = new HashSet<>(Arrays.asList(refUsages.get())); RenameUtil.removeConflictUsages(usagesSet); @@ -1053,18 +1054,21 @@ public MultiMap findConflicts(SimpleReference r for (UsageInfo usageInfo : usagesSet) { PsiElement element = usageInfo.getElement(); if (usageInfo instanceof OverriderUsageInfo overriderUsageInfo) { - PsiMethod method = (PsiMethod)element; + PsiMethod method = (PsiMethod) element; PsiMethod baseMethod = overriderUsageInfo.getBaseMethod(); int delta = baseMethod.getParameterList().getParametersCount() - method.getParameterList().getParametersCount(); if (delta > 0) { boolean[] toRemove = myChangeInfo.toRemoveParm(); if (toRemove[toRemove.length - 1]) { //todo check if implicit parameter is not the last one - conflictDescriptions.putValue(baseMethod, "Implicit last parameter should not be deleted"); + conflictDescriptions.putValue( + baseMethod, + LocalizeValue.localizeTODO("Implicit last parameter should not be deleted") + ); } } } else if (element instanceof PsiMethodReferenceExpression) { - conflictDescriptions.putValue(element, "Changed method is used in method reference"); + conflictDescriptions.putValue(element, LocalizeValue.localizeTODO("Changed method is used in method reference")); } } @@ -1078,10 +1082,10 @@ private boolean needToChangeCalls() { private void addInaccessibilityDescriptions( Set usages, - MultiMap conflictDescriptions + MultiMap conflictDescriptions ) throws IncorrectOperationException { PsiMethod method = myChangeInfo.getMethod(); - PsiModifierList modifierList = (PsiModifierList)method.getModifierList().copy(); + PsiModifierList modifierList = (PsiModifierList) method.getModifierList().copy(); VisibilityUtil.setVisibility(modifierList, myChangeInfo.getNewVisibility()); for (Iterator iterator = usages.iterator(); iterator.hasNext(); ) { @@ -1090,7 +1094,7 @@ private void addInaccessibilityDescriptions( if (element != null) { if (element instanceof PsiQualifiedReference qualifiedRef) { PsiClass accessObjectClass = qualifiedRef.getQualifier() instanceof PsiExpression expression - ? (PsiClass)PsiUtil.getAccessObjectClass(expression).getElement() + ? (PsiClass) PsiUtil.getAccessObjectClass(expression).getElement() : null; if (!JavaPsiFacade.getInstance(element.getProject()).getResolveHelper() @@ -1100,7 +1104,7 @@ private void addInaccessibilityDescriptions( VisibilityUtil.toPresentableText(myChangeInfo.getNewVisibility()), RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true) ); - conflictDescriptions.putValue(method, message.get()); + conflictDescriptions.putValue(method, message); if (!needToChangeCalls()) { iterator.remove(); } @@ -1111,7 +1115,7 @@ private void addInaccessibilityDescriptions( } @RequiredReadAction - private void addMethodConflicts(MultiMap conflicts) { + private void addMethodConflicts(MultiMap conflicts) { String newMethodName = myChangeInfo.getNewName(); if (!(myChangeInfo instanceof JavaChangeInfo)) { return; diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractInterface/ExtractInterfaceHandler.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractInterface/ExtractInterfaceHandler.java index 680a5b098..1a00bdd37 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractInterface/ExtractInterfaceHandler.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractInterface/ExtractInterfaceHandler.java @@ -20,7 +20,8 @@ import com.intellij.java.impl.refactoring.memberPullUp.PullUpProcessor; import com.intellij.java.impl.refactoring.util.classMembers.MemberInfo; import com.intellij.java.language.psi.*; -import consulo.application.ApplicationManager; +import consulo.annotation.access.RequiredReadAction; +import consulo.annotation.access.RequiredWriteAction; import consulo.codeEditor.Editor; import consulo.codeEditor.ScrollType; import consulo.dataContext.DataContext; @@ -37,6 +38,7 @@ import consulo.localize.LocalizeValue; import consulo.logging.Logger; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.undoRedo.CommandProcessor; import consulo.util.collection.ArrayUtil; import consulo.util.collection.MultiMap; @@ -45,112 +47,127 @@ import javax.swing.*; public class ExtractInterfaceHandler implements RefactoringActionHandler, ElementsHandler { - private static final Logger LOG = Logger.getInstance(ExtractInterfaceHandler.class); - - public static final LocalizeValue REFACTORING_NAME = RefactoringLocalize.extractInterfaceTitle(); - - private Project myProject; - private PsiClass myClass; - - private String myInterfaceName; - private MemberInfo[] mySelectedMembers; - private PsiDirectory myTargetDir; - private DocCommentPolicy myJavaDocPolicy; - - public void invoke(@Nonnull Project project, Editor editor, PsiFile file, DataContext dataContext) { - int offset = editor.getCaretModel().getOffset(); - editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); - PsiElement element = file.findElementAt(offset); - while (true) { - if (element == null || element instanceof PsiFile) { - LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.errorWrongCaretPositionClass()); - CommonRefactoringUtil.showErrorHint(project, editor, message.get(), REFACTORING_NAME.get(), HelpID.EXTRACT_INTERFACE); - return; - } - if (element instanceof PsiClass && !(element instanceof PsiAnonymousClass)) { - invoke(project, new PsiElement[]{element}, dataContext); - return; - } - element = element.getParent(); - } - } - - public void invoke(@Nonnull final Project project, @Nonnull PsiElement[] elements, DataContext dataContext) { - if (elements.length != 1) return; - - myProject = project; - myClass = (PsiClass) elements[0]; - - - if (!CommonRefactoringUtil.checkReadOnlyStatus(project, myClass)) return; - - final ExtractInterfaceDialog dialog = new ExtractInterfaceDialog(myProject, myClass); - dialog.show(); - if (!dialog.isOK() || !dialog.isExtractSuperclass()) return; - final MultiMap conflicts = new MultiMap(); - ExtractSuperClassUtil.checkSuperAccessible(dialog.getTargetDirectory(), conflicts, myClass); - if (!ExtractSuperClassUtil.showConflicts(dialog, conflicts, myProject)) return; - CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { - public void run() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - public void run() { - myInterfaceName = dialog.getExtractedSuperName(); - mySelectedMembers = ArrayUtil.toObjectArray(dialog.getSelectedMemberInfos(), MemberInfo.class); - myTargetDir = dialog.getTargetDirectory(); - myJavaDocPolicy = new DocCommentPolicy(dialog.getDocCommentPolicy()); - try { - doRefactoring(); - } catch (IncorrectOperationException e) { - LOG.error(e); + private static final Logger LOG = Logger.getInstance(ExtractInterfaceHandler.class); + + public static final LocalizeValue REFACTORING_NAME = RefactoringLocalize.extractInterfaceTitle(); + + private Project myProject; + private PsiClass myClass; + + private String myInterfaceName; + private MemberInfo[] mySelectedMembers; + private PsiDirectory myTargetDir; + private DocCommentPolicy myJavaDocPolicy; + + @Override + @RequiredUIAccess + public void invoke(@Nonnull Project project, Editor editor, PsiFile file, DataContext dataContext) { + int offset = editor.getCaretModel().getOffset(); + editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); + PsiElement element = file.findElementAt(offset); + while (true) { + if (element == null || element instanceof PsiFile) { + LocalizeValue message = + RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.errorWrongCaretPositionClass()); + CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.EXTRACT_INTERFACE); + return; + } + if (element instanceof PsiClass && !(element instanceof PsiAnonymousClass)) { + invoke(project, new PsiElement[]{element}, dataContext); + return; } - } - }); - } - }, REFACTORING_NAME.get(), null); - } - - private void doRefactoring() throws IncorrectOperationException { - LocalHistoryAction a = LocalHistory.getInstance().startAction(getCommandName()); - final PsiClass anInterface; - try { - anInterface = extractInterface(myTargetDir, myClass, myInterfaceName, mySelectedMembers, myJavaDocPolicy); - } finally { - a.finish(); + element = element.getParent(); + } } - if (anInterface != null) { - final SmartPsiElementPointer classPointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(myClass); - final SmartPsiElementPointer interfacePointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(anInterface); - final Runnable turnRefsToSuperRunnable = new Runnable() { - @Override - public void run() { - ExtractClassUtil.askAndTurnRefsToSuper(myProject, classPointer, interfacePointer); + @Override + @RequiredUIAccess + public void invoke(@Nonnull Project project, @Nonnull PsiElement[] elements, DataContext dataContext) { + if (elements.length != 1) { + return; + } + + myProject = project; + myClass = (PsiClass) elements[0]; + + if (!CommonRefactoringUtil.checkReadOnlyStatus(project, myClass)) { + return; } - }; - SwingUtilities.invokeLater(turnRefsToSuperRunnable); + + ExtractInterfaceDialog dialog = new ExtractInterfaceDialog(myProject, myClass); + dialog.show(); + if (!dialog.isOK() || !dialog.isExtractSuperclass()) { + return; + } + MultiMap conflicts = new MultiMap<>(); + ExtractSuperClassUtil.checkSuperAccessible(dialog.getTargetDirectory(), conflicts, myClass); + if (!ExtractSuperClassUtil.showConflicts(dialog, conflicts, myProject)) { + return; + } + CommandProcessor.getInstance().newCommand() + .project(myProject) + .name(REFACTORING_NAME) + .inWriteAction() + .run(() -> { + myInterfaceName = dialog.getExtractedSuperName(); + mySelectedMembers = ArrayUtil.toObjectArray(dialog.getSelectedMemberInfos(), MemberInfo.class); + myTargetDir = dialog.getTargetDirectory(); + myJavaDocPolicy = new DocCommentPolicy(dialog.getDocCommentPolicy()); + try { + doRefactoring(); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + }); + } + + @RequiredWriteAction + private void doRefactoring() throws IncorrectOperationException { + LocalHistoryAction a = LocalHistory.getInstance().startAction(getCommandName()); + PsiClass anInterface; + try { + anInterface = extractInterface(myTargetDir, myClass, myInterfaceName, mySelectedMembers, myJavaDocPolicy); + } + finally { + a.finish(); + } + + if (anInterface != null) { + SmartPsiElementPointer classPointer = + SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(myClass); + SmartPsiElementPointer interfacePointer = + SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(anInterface); + Runnable turnRefsToSuperRunnable = () -> ExtractClassUtil.askAndTurnRefsToSuper(myProject, classPointer, interfacePointer); + SwingUtilities.invokeLater(turnRefsToSuperRunnable); + } + } + + @RequiredWriteAction + static PsiClass extractInterface( + PsiDirectory targetDir, + PsiClass aClass, + String interfaceName, + MemberInfo[] selectedMembers, + DocCommentPolicy javaDocPolicy + ) throws IncorrectOperationException { + PsiClass anInterface = JavaDirectoryService.getInstance().createInterface(targetDir, interfaceName); + PsiJavaCodeReferenceElement ref = ExtractSuperClassUtil.createExtendingReference(anInterface, aClass, selectedMembers); + PsiReferenceList referenceList = aClass.isInterface() ? aClass.getExtendsList() : aClass.getImplementsList(); + assert referenceList != null; + referenceList.add(ref); + PullUpProcessor pullUpHelper = new PullUpProcessor(aClass, anInterface, selectedMembers, javaDocPolicy); + pullUpHelper.moveMembersToBase(); + return anInterface; + } + + @RequiredReadAction + private String getCommandName() { + return RefactoringLocalize.extractInterfaceCommandName(myInterfaceName, DescriptiveNameUtil.getDescriptiveName(myClass)).get(); + } + + @Override + public boolean isEnabledOnElements(PsiElement[] elements) { + return elements.length == 1 && elements[0] instanceof PsiClass; } - } - - static PsiClass extractInterface(PsiDirectory targetDir, - PsiClass aClass, - String interfaceName, - MemberInfo[] selectedMembers, - DocCommentPolicy javaDocPolicy) throws IncorrectOperationException { - PsiClass anInterface = JavaDirectoryService.getInstance().createInterface(targetDir, interfaceName); - PsiJavaCodeReferenceElement ref = ExtractSuperClassUtil.createExtendingReference(anInterface, aClass, selectedMembers); - final PsiReferenceList referenceList = aClass.isInterface() ? aClass.getExtendsList() : aClass.getImplementsList(); - assert referenceList != null; - referenceList.add(ref); - PullUpProcessor pullUpHelper = new PullUpProcessor(aClass, anInterface, selectedMembers, javaDocPolicy); - pullUpHelper.moveMembersToBase(); - return anInterface; - } - - private String getCommandName() { - return RefactoringLocalize.extractInterfaceCommandName(myInterfaceName, DescriptiveNameUtil.getDescriptiveName(myClass)).get(); - } - - public boolean isEnabledOnElements(PsiElement[] elements) { - return elements.length == 1 && elements[0] instanceof PsiClass; - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodDialog.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodDialog.java index e7a13a600..be8e613b8 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodDialog.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodDialog.java @@ -204,7 +204,7 @@ protected String getHelpId() { @Override protected void doOKAction() { - MultiMap conflicts = new MultiMap<>(); + MultiMap conflicts = new MultiMap<>(); checkMethodConflicts(conflicts); if (!conflicts.isEmpty()) { final ConflictsDialog conflictsDialog = new ConflictsDialog(myProject, conflicts); @@ -610,7 +610,7 @@ protected String getDimensionServiceKey() { return "extract.method.dialog"; } - protected void checkMethodConflicts(MultiMap conflicts) { + protected void checkMethodConflicts(MultiMap conflicts) { PsiMethod prototype; try { PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory(); diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodHandler.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodHandler.java index 5513408f2..68265e7cf 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodHandler.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodHandler.java @@ -48,6 +48,7 @@ import consulo.navigation.OpenFileDescriptor; import consulo.navigation.OpenFileDescriptorFactory; import consulo.project.Project; +import consulo.ui.annotation.RequiredUIAccess; import consulo.undoRedo.CommandProcessor; import consulo.virtualFileSystem.VirtualFile; import jakarta.annotation.Nonnull; @@ -57,184 +58,208 @@ import java.util.function.Consumer; public class ExtractMethodHandler implements RefactoringActionHandler { - private static final Logger LOG = Logger.getInstance(ExtractMethodHandler.class); - - public static final String REFACTORING_NAME = RefactoringBundle.message("extract.method.title"); - - @Override - public void invoke(@Nonnull Project project, @Nonnull PsiElement[] elements, DataContext dataContext) { - if (dataContext != null) { - final PsiFile file = dataContext.getData(PsiFile.KEY); - final Editor editor = dataContext.getData(Editor.KEY); - if (file != null && editor != null) { - invokeOnElements(project, editor, file, elements); - } + private static final Logger LOG = Logger.getInstance(ExtractMethodHandler.class); + + public static final LocalizeValue REFACTORING_NAME = RefactoringLocalize.extractMethodTitle(); + + @Override + public void invoke(@Nonnull Project project, @Nonnull PsiElement[] elements, DataContext dataContext) { + if (dataContext != null) { + final PsiFile file = dataContext.getData(PsiFile.KEY); + final Editor editor = dataContext.getData(Editor.KEY); + if (file != null && editor != null) { + invokeOnElements(project, editor, file, elements); + } + } } - } - - @Override - public void invoke(@Nonnull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) { - final Consumer callback = selectedValue -> invokeOnElements(project, editor, file, selectedValue); - selectAndPass(project, editor, file, callback); - } - - public static void selectAndPass(@Nonnull final Project project, @Nonnull final Editor editor, @Nonnull final PsiFile file, @Nonnull final Consumer callback) { - editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); - if (!editor.getSelectionModel().hasSelection()) { - final int offset = editor.getCaretModel().getOffset(); - final List expressions = IntroduceVariableBase.collectExpressions(file, editor, offset, true); - if (expressions.isEmpty()) { - editor.getSelectionModel().selectLineAtCaret(); - } else if (expressions.size() == 1) { - callback.accept(new PsiElement[]{expressions.get(0)}); - return; - } else { - IntroduceTargetChooser.showChooser( - editor, - expressions, - psiExpression -> callback.accept(new PsiElement[]{psiExpression}), - new PsiExpressionTrimRenderer.RenderFunction() - ); - return; - } + + @Override + public void invoke(@Nonnull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) { + final Consumer callback = selectedValue -> invokeOnElements(project, editor, file, selectedValue); + selectAndPass(project, editor, file, callback); } - int startOffset = editor.getSelectionModel().getSelectionStart(); - int endOffset = editor.getSelectionModel().getSelectionEnd(); - - PsiDocumentManager.getInstance(project).commitAllDocuments(); - - PsiElement[] elements; - PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); - if (expr != null) { - elements = new PsiElement[]{expr}; - } else { - elements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); - if (elements.length == 0) { - final PsiExpression expression = IntroduceVariableBase.getSelectedExpression(project, file, startOffset, endOffset); - if (expression != null && IntroduceVariableBase.getErrorMessage(expression) == null) { - final PsiType originalType = RefactoringUtil.getTypeByExpressionWithExpectedType(expression); - if (originalType != null) { - elements = new PsiElement[]{expression}; - } + public static void selectAndPass( + @Nonnull final Project project, + @Nonnull final Editor editor, + @Nonnull final PsiFile file, + @Nonnull final Consumer callback + ) { + editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); + if (!editor.getSelectionModel().hasSelection()) { + final int offset = editor.getCaretModel().getOffset(); + final List expressions = IntroduceVariableBase.collectExpressions(file, editor, offset, true); + if (expressions.isEmpty()) { + editor.getSelectionModel().selectLineAtCaret(); + } + else if (expressions.size() == 1) { + callback.accept(new PsiElement[]{expressions.get(0)}); + return; + } + else { + IntroduceTargetChooser.showChooser( + editor, + expressions, + psiExpression -> callback.accept(new PsiElement[]{psiExpression}), + new PsiExpressionTrimRenderer.RenderFunction() + ); + return; + } + } + + int startOffset = editor.getSelectionModel().getSelectionStart(); + int endOffset = editor.getSelectionModel().getSelectionEnd(); + + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + PsiElement[] elements; + PsiExpression expr = CodeInsightUtil.findExpressionInRange(file, startOffset, endOffset); + if (expr != null) { + elements = new PsiElement[]{expr}; } - } + else { + elements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); + if (elements.length == 0) { + final PsiExpression expression = IntroduceVariableBase.getSelectedExpression(project, file, startOffset, endOffset); + if (expression != null && IntroduceVariableBase.getErrorMessage(expression) == null) { + final PsiType originalType = RefactoringUtil.getTypeByExpressionWithExpectedType(expression); + if (originalType != null) { + elements = new PsiElement[]{expression}; + } + } + } + } + callback.accept(elements); } - callback.accept(elements); - } - private static void invokeOnElements(final Project project, final Editor editor, PsiFile file, PsiElement[] elements) { - getProcessor(elements, project, file, editor, true, processor -> invokeOnElements(project, editor, processor, true)); - } + private static void invokeOnElements(final Project project, final Editor editor, PsiFile file, PsiElement[] elements) { + getProcessor(elements, project, file, editor, true, processor -> invokeOnElements(project, editor, processor, true)); + } - private static boolean invokeOnElements(final Project project, final Editor editor, @Nonnull final ExtractMethodProcessor processor, final boolean directTypes) { - if (!CommonRefactoringUtil.checkReadOnlyStatus(project, processor.getTargetClass().getContainingFile())) { - return false; + private static boolean invokeOnElements( + final Project project, + final Editor editor, + @Nonnull final ExtractMethodProcessor processor, + final boolean directTypes + ) { + if (!CommonRefactoringUtil.checkReadOnlyStatus(project, processor.getTargetClass().getContainingFile())) { + return false; + } + if (processor.showDialog(directTypes)) { + run(project, editor, processor); + DuplicatesImpl.processDuplicates(processor, project, editor); + return true; + } + return false; } - if (processor.showDialog(directTypes)) { - run(project, editor, processor); - DuplicatesImpl.processDuplicates(processor, project, editor); - return true; + + public static void run(@Nonnull final Project project, final Editor editor, final ExtractMethodProcessor processor) { + CommandProcessor.getInstance().executeCommand( + project, + () -> PostprocessReformattingAspect.getInstance(project).postponeFormattingInside(() -> { + try { + processor.doRefactoring(); + } + catch (IncorrectOperationException e) { + LOG.error(e); + } + }), + REFACTORING_NAME.get(), + null + ); } - return false; - } - public static void run(@Nonnull final Project project, final Editor editor, final ExtractMethodProcessor processor) { - CommandProcessor.getInstance().executeCommand( - project, - () -> PostprocessReformattingAspect.getInstance(project).postponeFormattingInside(() -> { + @Nullable + @RequiredUIAccess + private static ExtractMethodProcessor getProcessor( + final PsiElement[] elements, + final Project project, + final PsiFile file, + final Editor editor, + final boolean showErrorMessages, + final @Nullable Consumer pass + ) { + if (elements == null || elements.length == 0) { + if (showErrorMessages) { + LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason( + RefactoringLocalize.selectedBlockShouldRepresentASetOfStatementsOrAnExpression() + ); + CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.EXTRACT_METHOD); + } + return null; + } + + for (PsiElement element : elements) { + if (element instanceof PsiStatement statement && JavaHighlightUtil.isSuperOrThisCall(statement, true, true)) { + if (showErrorMessages) { + LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason( + RefactoringLocalize.selectedBlockContainsInvocationOfAnotherClassConstructor() + ); + CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.EXTRACT_METHOD); + } + return null; + } + } + + final ExtractMethodProcessor processor = + new ExtractMethodProcessor(project, editor, elements, null, REFACTORING_NAME, "", HelpID.EXTRACT_METHOD); + processor.setShowErrorDialogs(showErrorMessages); try { - processor.doRefactoring(); - } catch (IncorrectOperationException e) { - LOG.error(e); + if (!processor.prepare(pass)) { + return null; + } } - }), - REFACTORING_NAME, - null - ); - } - - @Nullable - private static ExtractMethodProcessor getProcessor( - final PsiElement[] elements, - final Project project, - final PsiFile file, - final Editor editor, - final boolean showErrorMessages, - final @Nullable Consumer pass - ) { - if (elements == null || elements.length == 0) { - if (showErrorMessages) { - LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason( - RefactoringLocalize.selectedBlockShouldRepresentASetOfStatementsOrAnExpression() - ); - CommonRefactoringUtil.showErrorHint(project, editor, message.get(), REFACTORING_NAME, HelpID.EXTRACT_METHOD); - } - return null; + catch (PrepareFailedException e) { + if (showErrorMessages) { + CommonRefactoringUtil.showErrorHint(project, editor, LocalizeValue.ofNullable(e.getMessage()), REFACTORING_NAME, HelpID.EXTRACT_METHOD); + highlightPrepareError(e, file, editor, project); + } + return null; + } + return processor; } - for (PsiElement element : elements) { - if (element instanceof PsiStatement statement && JavaHighlightUtil.isSuperOrThisCall(statement, true, true)) { - if (showErrorMessages) { - LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason( - RefactoringLocalize.selectedBlockContainsInvocationOfAnotherClassConstructor() - ); - CommonRefactoringUtil.showErrorHint(project, editor, message.get(), REFACTORING_NAME, HelpID.EXTRACT_METHOD); + public static void highlightPrepareError(PrepareFailedException e, PsiFile file, Editor editor, final Project project) { + if (e.getFile() == file) { + final TextRange textRange = e.getTextRange(); + final HighlightManager highlightManager = HighlightManager.getInstance(project); + highlightManager.addRangeHighlight( + editor, + textRange.getStartOffset(), + textRange.getEndOffset(), + EditorColors.SEARCH_RESULT_ATTRIBUTES, + true, + null + ); + final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(textRange.getStartOffset()); + editor.getScrollingModel().scrollTo(logicalPosition, ScrollType.MAKE_VISIBLE); } - return null; - } } - final ExtractMethodProcessor processor = new ExtractMethodProcessor(project, editor, elements, null, REFACTORING_NAME, "", HelpID.EXTRACT_METHOD); - processor.setShowErrorDialogs(showErrorMessages); - try { - if (!processor.prepare(pass)) { - return null; - } - } catch (PrepareFailedException e) { - if (showErrorMessages) { - CommonRefactoringUtil.showErrorHint(project, editor, e.getMessage(), REFACTORING_NAME, HelpID.EXTRACT_METHOD); - highlightPrepareError(e, file, editor, project); - } - return null; + @Nullable + public static ExtractMethodProcessor getProcessor( + final Project project, + final PsiElement[] elements, + final PsiFile file, + final boolean openEditor + ) { + return getProcessor(elements, project, file, openEditor ? openEditor(project, file) : null, false, null); } - return processor; - } - - public static void highlightPrepareError(PrepareFailedException e, PsiFile file, Editor editor, final Project project) { - if (e.getFile() == file) { - final TextRange textRange = e.getTextRange(); - final HighlightManager highlightManager = HighlightManager.getInstance(project); - highlightManager.addRangeHighlight(editor, textRange.getStartOffset(), textRange.getEndOffset(), EditorColors.SEARCH_RESULT_ATTRIBUTES, true, null); - final LogicalPosition logicalPosition = editor.offsetToLogicalPosition(textRange.getStartOffset()); - editor.getScrollingModel().scrollTo(logicalPosition, ScrollType.MAKE_VISIBLE); + + public static boolean invokeOnElements( + final Project project, + @Nonnull final ExtractMethodProcessor processor, + final PsiFile file, + final boolean directTypes + ) { + return invokeOnElements(project, openEditor(project, file), processor, directTypes); + } + + @Nullable + private static Editor openEditor(final Project project, final PsiFile file) { + final VirtualFile virtualFile = file.getVirtualFile(); + LOG.assertTrue(virtualFile != null); + final OpenFileDescriptor fileDescriptor = OpenFileDescriptorFactory.getInstance(project).newBuilder(virtualFile).build(); + return FileEditorManager.getInstance(project).openTextEditor(fileDescriptor, false); } - } - - @Nullable - public static ExtractMethodProcessor getProcessor( - final Project project, - final PsiElement[] elements, - final PsiFile file, - final boolean openEditor - ) { - return getProcessor(elements, project, file, openEditor ? openEditor(project, file) : null, false, null); - } - - public static boolean invokeOnElements( - final Project project, - @Nonnull final ExtractMethodProcessor processor, - final PsiFile file, - final boolean directTypes - ) { - return invokeOnElements(project, openEditor(project, file), processor, directTypes); - } - - @Nullable - private static Editor openEditor(final Project project, final PsiFile file) { - final VirtualFile virtualFile = file.getVirtualFile(); - LOG.assertTrue(virtualFile != null); - final OpenFileDescriptor fileDescriptor = OpenFileDescriptorFactory.getInstance(project).newBuilder(virtualFile).build(); - return FileEditorManager.getInstance(project).openTextEditor(fileDescriptor, false); - } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodProcessor.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodProcessor.java index 7f058a6df..f9ab771d9 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodProcessor.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethod/ExtractMethodProcessor.java @@ -98,1738 +98,1880 @@ import static com.intellij.java.language.codeInsight.AnnotationUtil.CHECK_TYPE; public class ExtractMethodProcessor implements MatchProvider { - private static final Logger LOG = Logger.getInstance(ExtractMethodProcessor.class); - - protected final Project myProject; - private final Editor myEditor; - protected final PsiElement[] myElements; - private final PsiBlockStatement myEnclosingBlockStatement; - private final PsiType myForcedReturnType; - private final String myRefactoringName; - protected final String myInitialMethodName; - private final String myHelpId; - - private final PsiManager myManager; - private final PsiElementFactory myElementFactory; - private final CodeStyleManager myStyleManager; - - private PsiExpression myExpression; - - private PsiElement myCodeFragmentMember; // parent of myCodeFragment - - protected String myMethodName; // name for extracted method - protected PsiType myReturnType; // return type for extracted method - protected PsiTypeParameterList myTypeParameterList; //type parameter list of extracted method - private VariableData[] myVariableDatum; // parameter data for extracted method - protected PsiClassType[] myThrownExceptions; // exception to declare as thrown by extracted method - protected boolean myStatic; // whether to declare extracted method static - - protected PsiClass myTargetClass; // class to create the extracted method in - private PsiElement myAnchor; // anchor to insert extracted method after it - - protected ControlFlowWrapper myControlFlowWrapper; - protected InputVariables myInputVariables; // input variables - protected PsiVariable[] myOutputVariables; // output variables - protected PsiVariable myOutputVariable; // the only output variable - private PsiVariable myArtificialOutputVariable; - private Collection myExitStatements; - - private boolean myHasReturnStatement; // there is a return statement - private boolean myHasReturnStatementOutput; // there is a return statement and its type is not void - protected boolean myHasExpressionOutput; // extracted code is an expression with non-void type - private boolean myNeedChangeContext; // target class is not immediate container of the code to be extracted - - private boolean myShowErrorDialogs = true; - protected boolean myCanBeStatic; - protected boolean myCanBeChainedConstructor; - protected boolean myIsChainedConstructor; - private List myDuplicates; - @PsiModifier.ModifierConstant - private String myMethodVisibility = PsiModifier.PRIVATE; - protected boolean myGenerateConditionalExit; - protected PsiStatement myFirstExitStatementCopy; - private PsiMethod myExtractedMethod; - private PsiMethodCallExpression myMethodCall; - protected boolean myNullConditionalCheck = false; - protected boolean myNotNullConditionalCheck = false; - protected Nullability myNullability; - - public ExtractMethodProcessor(Project project, Editor editor, PsiElement[] elements, PsiType forcedReturnType, String refactoringName, String initialMethodName, String helpId) { - myProject = project; - myEditor = editor; - if (elements.length != 1 || !(elements[0] instanceof PsiBlockStatement)) { - myElements = elements.length == 1 && elements[0] instanceof PsiParenthesizedExpression ? new PsiElement[]{PsiUtil.skipParenthesizedExprDown((PsiExpression) elements[0])} : elements; - myEnclosingBlockStatement = null; - } else { - myEnclosingBlockStatement = (PsiBlockStatement) elements[0]; - PsiElement[] codeBlockChildren = myEnclosingBlockStatement.getCodeBlock().getChildren(); - myElements = processCodeBlockChildren(codeBlockChildren); - } - myForcedReturnType = forcedReturnType; - myRefactoringName = refactoringName; - myInitialMethodName = initialMethodName; - myHelpId = helpId; - - myManager = PsiManager.getInstance(myProject); - myElementFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); - myStyleManager = CodeStyleManager.getInstance(myProject); - } - - private static PsiElement[] processCodeBlockChildren(PsiElement[] codeBlockChildren) { - int resultLast = codeBlockChildren.length; - - if (codeBlockChildren.length == 0) { - return PsiElement.EMPTY_ARRAY; - } - - final PsiElement first = codeBlockChildren[0]; - int resultStart = 0; - if (first instanceof PsiJavaToken && ((PsiJavaToken) first).getTokenType() == JavaTokenType.LBRACE) { - resultStart++; - } - final PsiElement last = codeBlockChildren[codeBlockChildren.length - 1]; - if (last instanceof PsiJavaToken && ((PsiJavaToken) last).getTokenType() == JavaTokenType.RBRACE) { - resultLast--; - } - final ArrayList result = new ArrayList<>(); - for (int i = resultStart; i < resultLast; i++) { - PsiElement element = codeBlockChildren[i]; - if (!(element instanceof PsiWhiteSpace)) { - result.add(element); - } - } - - return PsiUtilCore.toPsiElementArray(result); - } - - /** - * Method for test purposes - */ - public void setShowErrorDialogs(boolean showErrorDialogs) { - myShowErrorDialogs = showErrorDialogs; - } - - public void setChainedConstructor(final boolean isChainedConstructor) { - myIsChainedConstructor = isChainedConstructor; - } - - - public boolean prepare() throws PrepareFailedException { - return prepare(null); - } - - /** - * Invoked in atomic action - */ - public boolean prepare(@Nullable Consumer pass) throws PrepareFailedException { - myExpression = null; - if (myElements.length == 1 && myElements[0] instanceof PsiExpression) { - final PsiExpression expression = (PsiExpression) myElements[0]; - if (expression instanceof PsiAssignmentExpression && expression.getParent() instanceof PsiExpressionStatement) { - myElements[0] = expression.getParent(); - } else { - myExpression = expression; - } - } - - final PsiElement codeFragment = ControlFlowUtil.findCodeFragment(myElements[0]); - myCodeFragmentMember = codeFragment.getUserData(ElementToWorkOn.PARENT); - if (myCodeFragmentMember == null) { - myCodeFragmentMember = codeFragment.getParent(); - } - if (myCodeFragmentMember == null) { - myCodeFragmentMember = ControlFlowUtil.findCodeFragment(codeFragment.getContext()).getParent(); - } - - myControlFlowWrapper = new ControlFlowWrapper(codeFragment, myElements); - - try { - myExitStatements = myControlFlowWrapper.prepareExitStatements(myElements); - if (myControlFlowWrapper.isGenerateConditionalExit()) { - myGenerateConditionalExit = true; - } else { - myHasReturnStatement = myExpression == null && myControlFlowWrapper.isReturnPresentBetween(); - } - myFirstExitStatementCopy = myControlFlowWrapper.getFirstExitStatementCopy(); - } catch (ControlFlowWrapper.ExitStatementsNotSameException e) { - myExitStatements = myControlFlowWrapper.getExitStatements(); - myNotNullConditionalCheck = areAllExitPointsAreNotNull(getExpectedReturnType()); - if (!myNotNullConditionalCheck) { - showMultipleExitPointsMessage(); - return false; - } + private static final Logger LOG = Logger.getInstance(ExtractMethodProcessor.class); + + protected final Project myProject; + private final Editor myEditor; + protected final PsiElement[] myElements; + private final PsiBlockStatement myEnclosingBlockStatement; + private final PsiType myForcedReturnType; + private final String myRefactoringName; + protected final String myInitialMethodName; + private final String myHelpId; + + private final PsiManager myManager; + private final PsiElementFactory myElementFactory; + private final CodeStyleManager myStyleManager; + + private PsiExpression myExpression; + + private PsiElement myCodeFragmentMember; // parent of myCodeFragment + + protected String myMethodName; // name for extracted method + protected PsiType myReturnType; // return type for extracted method + protected PsiTypeParameterList myTypeParameterList; //type parameter list of extracted method + private VariableData[] myVariableDatum; // parameter data for extracted method + protected PsiClassType[] myThrownExceptions; // exception to declare as thrown by extracted method + protected boolean myStatic; // whether to declare extracted method static + + protected PsiClass myTargetClass; // class to create the extracted method in + private PsiElement myAnchor; // anchor to insert extracted method after it + + protected ControlFlowWrapper myControlFlowWrapper; + protected InputVariables myInputVariables; // input variables + protected PsiVariable[] myOutputVariables; // output variables + protected PsiVariable myOutputVariable; // the only output variable + private PsiVariable myArtificialOutputVariable; + private Collection myExitStatements; + + private boolean myHasReturnStatement; // there is a return statement + private boolean myHasReturnStatementOutput; // there is a return statement and its type is not void + protected boolean myHasExpressionOutput; // extracted code is an expression with non-void type + private boolean myNeedChangeContext; // target class is not immediate container of the code to be extracted + + private boolean myShowErrorDialogs = true; + protected boolean myCanBeStatic; + protected boolean myCanBeChainedConstructor; + protected boolean myIsChainedConstructor; + private List myDuplicates; + @PsiModifier.ModifierConstant + private String myMethodVisibility = PsiModifier.PRIVATE; + protected boolean myGenerateConditionalExit; + protected PsiStatement myFirstExitStatementCopy; + private PsiMethod myExtractedMethod; + private PsiMethodCallExpression myMethodCall; + protected boolean myNullConditionalCheck = false; + protected boolean myNotNullConditionalCheck = false; + protected Nullability myNullability; + + public ExtractMethodProcessor( + Project project, + Editor editor, + PsiElement[] elements, + PsiType forcedReturnType, + String refactoringName, + String initialMethodName, + String helpId + ) { + myProject = project; + myEditor = editor; + if (elements.length != 1 || !(elements[0] instanceof PsiBlockStatement)) { + myElements = + elements.length == 1 && elements[0] instanceof PsiParenthesizedExpression ? new PsiElement[]{PsiUtil.skipParenthesizedExprDown( + (PsiExpression) elements[0])} : elements; + myEnclosingBlockStatement = null; + } + else { + myEnclosingBlockStatement = (PsiBlockStatement) elements[0]; + PsiElement[] codeBlockChildren = myEnclosingBlockStatement.getCodeBlock().getChildren(); + myElements = processCodeBlockChildren(codeBlockChildren); + } + myForcedReturnType = forcedReturnType; + myRefactoringName = refactoringName; + myInitialMethodName = initialMethodName; + myHelpId = helpId; + + myManager = PsiManager.getInstance(myProject); + myElementFactory = JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory(); + myStyleManager = CodeStyleManager.getInstance(myProject); } - myOutputVariables = myControlFlowWrapper.getOutputVariables(); + private static PsiElement[] processCodeBlockChildren(PsiElement[] codeBlockChildren) { + int resultLast = codeBlockChildren.length; - return chooseTargetClass(codeFragment, pass); - } + if (codeBlockChildren.length == 0) { + return PsiElement.EMPTY_ARRAY; + } - private boolean checkExitPoints() throws PrepareFailedException { - PsiType expressionType = null; - if (myExpression != null) { - if (myForcedReturnType != null) { - expressionType = myForcedReturnType; - } else { - expressionType = RefactoringUtil.getTypeByExpressionWithExpectedType(myExpression); - if (expressionType == null && !(myExpression.getParent() instanceof PsiExpressionStatement)) { - expressionType = PsiType.getJavaLangObject(myExpression.getManager(), GlobalSearchScope.allScope(myProject)); + final PsiElement first = codeBlockChildren[0]; + int resultStart = 0; + if (first instanceof PsiJavaToken && ((PsiJavaToken) first).getTokenType() == JavaTokenType.LBRACE) { + resultStart++; + } + final PsiElement last = codeBlockChildren[codeBlockChildren.length - 1]; + if (last instanceof PsiJavaToken && ((PsiJavaToken) last).getTokenType() == JavaTokenType.RBRACE) { + resultLast--; + } + final ArrayList result = new ArrayList<>(); + for (int i = resultStart; i < resultLast; i++) { + PsiElement element = codeBlockChildren[i]; + if (!(element instanceof PsiWhiteSpace)) { + result.add(element); + } } - } + + return PsiUtilCore.toPsiElementArray(result); + } + + /** + * Method for test purposes + */ + public void setShowErrorDialogs(boolean showErrorDialogs) { + myShowErrorDialogs = showErrorDialogs; } - if (expressionType == null) { - expressionType = PsiType.VOID; + + public void setChainedConstructor(final boolean isChainedConstructor) { + myIsChainedConstructor = isChainedConstructor; + } + + + public boolean prepare() throws PrepareFailedException { + return prepare(null); } - myHasExpressionOutput = !PsiType.VOID.equals(expressionType); - final PsiType returnStatementType = getExpectedReturnType(); - myHasReturnStatementOutput = myHasReturnStatement && returnStatementType != null && !PsiType.VOID.equals(returnStatementType); + /** + * Invoked in atomic action + */ + public boolean prepare(@Nullable Consumer pass) throws PrepareFailedException { + myExpression = null; + if (myElements.length == 1 && myElements[0] instanceof PsiExpression) { + final PsiExpression expression = (PsiExpression) myElements[0]; + if (expression instanceof PsiAssignmentExpression && expression.getParent() instanceof PsiExpressionStatement) { + myElements[0] = expression.getParent(); + } + else { + myExpression = expression; + } + } - if (myGenerateConditionalExit && myOutputVariables.length == 1) { - if (!(myOutputVariables[0].getType() instanceof PsiPrimitiveType)) { - myNullConditionalCheck = true; - for (PsiStatement exitStatement : myExitStatements) { - if (exitStatement instanceof PsiReturnStatement) { - final PsiExpression returnValue = ((PsiReturnStatement) exitStatement).getReturnValue(); - myNullConditionalCheck &= returnValue == null || isNullInferred(returnValue.getText(), true); - } + final PsiElement codeFragment = ControlFlowUtil.findCodeFragment(myElements[0]); + myCodeFragmentMember = codeFragment.getUserData(ElementToWorkOn.PARENT); + if (myCodeFragmentMember == null) { + myCodeFragmentMember = codeFragment.getParent(); + } + if (myCodeFragmentMember == null) { + myCodeFragmentMember = ControlFlowUtil.findCodeFragment(codeFragment.getContext()).getParent(); } - myNullConditionalCheck &= isNullInferred(myOutputVariables[0].getName(), false); - } - myNotNullConditionalCheck = areAllExitPointsAreNotNull(returnStatementType); + myControlFlowWrapper = new ControlFlowWrapper(codeFragment, myElements); + + try { + myExitStatements = myControlFlowWrapper.prepareExitStatements(myElements); + if (myControlFlowWrapper.isGenerateConditionalExit()) { + myGenerateConditionalExit = true; + } + else { + myHasReturnStatement = myExpression == null && myControlFlowWrapper.isReturnPresentBetween(); + } + myFirstExitStatementCopy = myControlFlowWrapper.getFirstExitStatementCopy(); + } + catch (ControlFlowWrapper.ExitStatementsNotSameException e) { + myExitStatements = myControlFlowWrapper.getExitStatements(); + myNotNullConditionalCheck = areAllExitPointsAreNotNull(getExpectedReturnType()); + if (!myNotNullConditionalCheck) { + showMultipleExitPointsMessage(); + return false; + } + } + + myOutputVariables = myControlFlowWrapper.getOutputVariables(); + + return chooseTargetClass(codeFragment, pass); } - if (!myHasReturnStatementOutput && checkOutputVariablesCount() && !myNullConditionalCheck && !myNotNullConditionalCheck) { - showMultipleOutputMessage(expressionType); - return false; + private boolean checkExitPoints() throws PrepareFailedException { + PsiType expressionType = null; + if (myExpression != null) { + if (myForcedReturnType != null) { + expressionType = myForcedReturnType; + } + else { + expressionType = RefactoringUtil.getTypeByExpressionWithExpectedType(myExpression); + if (expressionType == null && !(myExpression.getParent() instanceof PsiExpressionStatement)) { + expressionType = PsiType.getJavaLangObject(myExpression.getManager(), GlobalSearchScope.allScope(myProject)); + } + } + } + if (expressionType == null) { + expressionType = PsiType.VOID; + } + myHasExpressionOutput = !PsiType.VOID.equals(expressionType); + + final PsiType returnStatementType = getExpectedReturnType(); + myHasReturnStatementOutput = myHasReturnStatement && returnStatementType != null && !PsiType.VOID.equals(returnStatementType); + + if (myGenerateConditionalExit && myOutputVariables.length == 1) { + if (!(myOutputVariables[0].getType() instanceof PsiPrimitiveType)) { + myNullConditionalCheck = true; + for (PsiStatement exitStatement : myExitStatements) { + if (exitStatement instanceof PsiReturnStatement) { + final PsiExpression returnValue = ((PsiReturnStatement) exitStatement).getReturnValue(); + myNullConditionalCheck &= returnValue == null || isNullInferred(returnValue.getText(), true); + } + } + myNullConditionalCheck &= isNullInferred(myOutputVariables[0].getName(), false); + } + + myNotNullConditionalCheck = areAllExitPointsAreNotNull(returnStatementType); + } + + if (!myHasReturnStatementOutput && checkOutputVariablesCount() && !myNullConditionalCheck && !myNotNullConditionalCheck) { + showMultipleOutputMessage(expressionType); + return false; + } + + myOutputVariable = myOutputVariables.length > 0 ? myOutputVariables[0] : null; + if (myNotNullConditionalCheck) { + myReturnType = returnStatementType instanceof PsiPrimitiveType primitiveType + ? primitiveType.getBoxedType(myCodeFragmentMember) : returnStatementType; + } + else if (myHasReturnStatementOutput) { + myReturnType = returnStatementType; + } + else if (myOutputVariable != null) { + myReturnType = myOutputVariable.getType(); + } + else if (myGenerateConditionalExit) { + myReturnType = PsiType.BOOLEAN; + } + else { + myReturnType = expressionType; + } + + PsiElement container = PsiTreeUtil.getParentOfType(myElements[0], PsiClass.class, PsiMethod.class); + while (container instanceof PsiMethod && ((PsiMethod) container).getContainingClass() != myTargetClass) { + container = PsiTreeUtil.getParentOfType(container, PsiMethod.class, true); + } + if (container instanceof PsiMethod method) { + PsiElement[] elements = myElements; + if (myExpression == null) { + if (myOutputVariable != null) { + elements = ArrayUtil.append(myElements, myOutputVariable, PsiElement.class); + } + if (myCodeFragmentMember instanceof PsiMethod codeFragmentMethod && myReturnType == codeFragmentMethod.getReturnType()) { + elements = ArrayUtil.append(myElements, codeFragmentMethod.getReturnTypeElement(), PsiElement.class); + } + } + myTypeParameterList = RefactoringUtil.createTypeParameterListWithUsedTypeParameters(method.getTypeParameterList(), elements); + } + List exceptions = ExceptionUtil.getThrownCheckedExceptions(myElements); + myThrownExceptions = exceptions.toArray(new PsiClassType[exceptions.size()]); + + if (container instanceof PsiMethod containerMethod) { + checkLocalClasses(containerMethod); + } + return true; } - myOutputVariable = myOutputVariables.length > 0 ? myOutputVariables[0] : null; - if (myNotNullConditionalCheck) { - myReturnType = returnStatementType instanceof PsiPrimitiveType primitiveType - ? primitiveType.getBoxedType(myCodeFragmentMember) : returnStatementType; - } else if (myHasReturnStatementOutput) { - myReturnType = returnStatementType; - } else if (myOutputVariable != null) { - myReturnType = myOutputVariable.getType(); - } else if (myGenerateConditionalExit) { - myReturnType = PsiType.BOOLEAN; - } else { - myReturnType = expressionType; + private PsiType getExpectedReturnType() { + return myCodeFragmentMember instanceof PsiMethod codeFragmentMethod + ? codeFragmentMethod.getReturnType() + : myCodeFragmentMember instanceof PsiLambdaExpression codeFragmentLambda + ? LambdaUtil.getFunctionalInterfaceReturnType(codeFragmentLambda) + : null; + } + + @Nullable + private PsiVariable getArtificialOutputVariable() { + if (myOutputVariables.length == 0 && myExitStatements.isEmpty()) { + if (myCanBeChainedConstructor) { + final Set fields = new HashSet<>(); + for (PsiElement element : myElements) { + element.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + super.visitReferenceExpression(expression); + final PsiElement resolve = expression.resolve(); + if (resolve instanceof PsiField field && field.hasModifierProperty(PsiModifier.FINAL) && + PsiUtil.isAccessedForWriting(expression)) { + fields.add((PsiField) resolve); + } + } + }); + } + if (!fields.isEmpty()) { + return fields.size() == 1 ? fields.iterator().next() : null; + } + } + final VariablesProcessor processor = new VariablesProcessor(true) { + @Override + protected boolean check(PsiVariable var, ResolveState state) { + return isDeclaredInside(var); + } + }; + PsiScopesUtil.treeWalkUp(processor, myElements[myElements.length - 1], myCodeFragmentMember); + if (processor.size() == 1) { + return processor.getResult(0); + } + } + return null; } - PsiElement container = PsiTreeUtil.getParentOfType(myElements[0], PsiClass.class, PsiMethod.class); - while (container instanceof PsiMethod && ((PsiMethod) container).getContainingClass() != myTargetClass) { - container = PsiTreeUtil.getParentOfType(container, PsiMethod.class, true); + private boolean areAllExitPointsAreNotNull(PsiType returnStatementType) { + if (insertNotNullCheckIfPossible() && myControlFlowWrapper.getOutputVariables(false).length == 0) { + boolean isNotNull = returnStatementType != null && !PsiType.VOID.equals(returnStatementType); + for (PsiStatement statement : myExitStatements) { + if (statement instanceof PsiReturnStatement) { + final PsiExpression returnValue = ((PsiReturnStatement) statement).getReturnValue(); + isNotNull &= returnValue != null && !isNullInferred(returnValue.getText(), true); + } + } + return isNotNull; + } + return false; } - if (container instanceof PsiMethod method) { - PsiElement[] elements = myElements; - if (myExpression == null) { - if (myOutputVariable != null) { - elements = ArrayUtil.append(myElements, myOutputVariable, PsiElement.class); + + protected boolean insertNotNullCheckIfPossible() { + return true; + } + + private boolean isNullInferred(String exprText, boolean trueSet) { + final PsiCodeBlock block = myElementFactory.createCodeBlockFromText("{}", myElements[0]); + for (PsiElement element : myElements) { + block.add(element); } - if (myCodeFragmentMember instanceof PsiMethod codeFragmentMethod && myReturnType == codeFragmentMethod.getReturnType()) { - elements = ArrayUtil.append(myElements, codeFragmentMethod.getReturnTypeElement(), PsiElement.class); + PsiReturnStatement statementFromText = + (PsiReturnStatement) myElementFactory.createStatementFromText("return " + exprText + ";", null); + statementFromText = (PsiReturnStatement) block.add(statementFromText); + + return inferNullability(block, Objects.requireNonNull(statementFromText.getReturnValue())) == Nullability.NOT_NULL; + } + + private static Nullability inferNullability(@Nonnull PsiCodeBlock block, @Nonnull PsiExpression expr) { + final DataFlowRunner dfaRunner = new DataFlowRunner(block.getProject()); + + class Visitor extends StandardInstructionVisitor { + DfaNullability myNullability = DfaNullability.NOT_NULL; + boolean myVisited = false; + + @Override + protected void beforeExpressionPush( + @Nonnull DfaValue value, + @Nonnull PsiExpression expression, + @Nullable TextRange range, + @Nonnull DfaMemoryState state + ) { + if (expression == expr && range == null) { + myVisited = true; + myNullability = myNullability.unite(DfaNullability.fromDfType(state.getDfType(value))); + } + } } - } - myTypeParameterList = RefactoringUtil.createTypeParameterListWithUsedTypeParameters(method.getTypeParameterList(), elements); + Visitor visitor = new Visitor(); + final RunnerResult rc = dfaRunner.analyzeMethod(block, visitor); + return rc == RunnerResult.OK && visitor.myVisited ? DfaNullability.toNullability(visitor.myNullability) : Nullability.UNKNOWN; } - List exceptions = ExceptionUtil.getThrownCheckedExceptions(myElements); - myThrownExceptions = exceptions.toArray(new PsiClassType[exceptions.size()]); - if (container instanceof PsiMethod containerMethod) { - checkLocalClasses(containerMethod); + protected boolean checkOutputVariablesCount() { + int outputCount = (myHasExpressionOutput ? 1 : 0) + (myGenerateConditionalExit ? 1 : 0) + myOutputVariables.length; + return outputCount > 1; } - return true; - } - private PsiType getExpectedReturnType() { - return myCodeFragmentMember instanceof PsiMethod codeFragmentMethod - ? codeFragmentMethod.getReturnType() - : myCodeFragmentMember instanceof PsiLambdaExpression codeFragmentLambda - ? LambdaUtil.getFunctionalInterfaceReturnType(codeFragmentLambda) - : null; - } + private void checkCanBeChainedConstructor() { + if (!(myCodeFragmentMember instanceof PsiMethod)) { + return; + } + final PsiMethod method = (PsiMethod) myCodeFragmentMember; + if (!method.isConstructor() || !PsiType.VOID.equals(myReturnType)) { + return; + } + final PsiCodeBlock body = method.getBody(); + if (body == null) { + return; + } + final PsiStatement[] psiStatements = body.getStatements(); + if (psiStatements.length > 0 && myElements[0] == psiStatements[0]) { + myCanBeChainedConstructor = true; + } + } - @Nullable - private PsiVariable getArtificialOutputVariable() { - if (myOutputVariables.length == 0 && myExitStatements.isEmpty()) { - if (myCanBeChainedConstructor) { - final Set fields = new HashSet<>(); - for (PsiElement element : myElements) { - element.accept(new JavaRecursiveElementWalkingVisitor() { + private void checkLocalClasses(final PsiMethod container) throws PrepareFailedException { + final List localClasses = new ArrayList<>(); + container.accept(new JavaRecursiveElementWalkingVisitor() { @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - super.visitReferenceExpression(expression); - final PsiElement resolve = expression.resolve(); - if (resolve instanceof PsiField field && field.hasModifierProperty(PsiModifier.FINAL) && - PsiUtil.isAccessedForWriting(expression)) { - fields.add((PsiField) resolve); - } - } - }); - } - if (!fields.isEmpty()) { - return fields.size() == 1 ? fields.iterator().next() : null; - } - } - final VariablesProcessor processor = new VariablesProcessor(true) { - @Override - protected boolean check(PsiVariable var, ResolveState state) { - return isDeclaredInside(var); - } - }; - PsiScopesUtil.treeWalkUp(processor, myElements[myElements.length - 1], myCodeFragmentMember); - if (processor.size() == 1) { - return processor.getResult(0); - } - } - return null; - } - - private boolean areAllExitPointsAreNotNull(PsiType returnStatementType) { - if (insertNotNullCheckIfPossible() && myControlFlowWrapper.getOutputVariables(false).length == 0) { - boolean isNotNull = returnStatementType != null && !PsiType.VOID.equals(returnStatementType); - for (PsiStatement statement : myExitStatements) { - if (statement instanceof PsiReturnStatement) { - final PsiExpression returnValue = ((PsiReturnStatement) statement).getReturnValue(); - isNotNull &= returnValue != null && !isNullInferred(returnValue.getText(), true); - } - } - return isNotNull; - } - return false; - } - - protected boolean insertNotNullCheckIfPossible() { - return true; - } - - private boolean isNullInferred(String exprText, boolean trueSet) { - final PsiCodeBlock block = myElementFactory.createCodeBlockFromText("{}", myElements[0]); - for (PsiElement element : myElements) { - block.add(element); - } - PsiReturnStatement statementFromText = (PsiReturnStatement) myElementFactory.createStatementFromText("return " + exprText + ";", null); - statementFromText = (PsiReturnStatement) block.add(statementFromText); - - return inferNullability(block, Objects.requireNonNull(statementFromText.getReturnValue())) == Nullability.NOT_NULL; - } - - private static Nullability inferNullability(@Nonnull PsiCodeBlock block, @Nonnull PsiExpression expr) { - final DataFlowRunner dfaRunner = new DataFlowRunner(block.getProject()); - - class Visitor extends StandardInstructionVisitor { - DfaNullability myNullability = DfaNullability.NOT_NULL; - boolean myVisited = false; - - @Override - protected void beforeExpressionPush(@Nonnull DfaValue value, - @Nonnull PsiExpression expression, - @Nullable TextRange range, - @Nonnull DfaMemoryState state) { - if (expression == expr && range == null) { - myVisited = true; - myNullability = myNullability.unite(DfaNullability.fromDfType(state.getDfType(value))); - } - } - } - Visitor visitor = new Visitor(); - final RunnerResult rc = dfaRunner.analyzeMethod(block, visitor); - return rc == RunnerResult.OK && visitor.myVisited ? DfaNullability.toNullability(visitor.myNullability) : Nullability.UNKNOWN; - } - - protected boolean checkOutputVariablesCount() { - int outputCount = (myHasExpressionOutput ? 1 : 0) + (myGenerateConditionalExit ? 1 : 0) + myOutputVariables.length; - return outputCount > 1; - } - - private void checkCanBeChainedConstructor() { - if (!(myCodeFragmentMember instanceof PsiMethod)) { - return; - } - final PsiMethod method = (PsiMethod) myCodeFragmentMember; - if (!method.isConstructor() || !PsiType.VOID.equals(myReturnType)) { - return; - } - final PsiCodeBlock body = method.getBody(); - if (body == null) { - return; - } - final PsiStatement[] psiStatements = body.getStatements(); - if (psiStatements.length > 0 && myElements[0] == psiStatements[0]) { - myCanBeChainedConstructor = true; - } - } - - private void checkLocalClasses(final PsiMethod container) throws PrepareFailedException { - final List localClasses = new ArrayList<>(); - container.accept(new JavaRecursiveElementWalkingVisitor() { - @Override - public void visitClass(final PsiClass aClass) { - localClasses.add(aClass); - } - - @Override - public void visitAnonymousClass(final PsiAnonymousClass aClass) { - visitElement(aClass); - } - - @Override - public void visitTypeParameter(final PsiTypeParameter classParameter) { - visitElement(classParameter); - } - }); - for (PsiClass localClass : localClasses) { - final boolean classExtracted = isExtractedElement(localClass); - final List extractedReferences = Collections.synchronizedList(new ArrayList()); - final List remainingReferences = Collections.synchronizedList(new ArrayList()); - ReferencesSearch.search(localClass).forEach(psiReference -> { - final PsiElement element = psiReference.getElement(); - final boolean elementExtracted = isExtractedElement(element); - if (elementExtracted && !classExtracted) { - extractedReferences.add(element); - return false; - } - if (!elementExtracted && classExtracted) { - remainingReferences.add(element); - return false; + public void visitClass(final PsiClass aClass) { + localClasses.add(aClass); + } + + @Override + public void visitAnonymousClass(final PsiAnonymousClass aClass) { + visitElement(aClass); + } + + @Override + public void visitTypeParameter(final PsiTypeParameter classParameter) { + visitElement(classParameter); + } + }); + for (PsiClass localClass : localClasses) { + final boolean classExtracted = isExtractedElement(localClass); + final List extractedReferences = Collections.synchronizedList(new ArrayList()); + final List remainingReferences = Collections.synchronizedList(new ArrayList()); + ReferencesSearch.search(localClass).forEach(psiReference -> { + final PsiElement element = psiReference.getElement(); + final boolean elementExtracted = isExtractedElement(element); + if (elementExtracted && !classExtracted) { + extractedReferences.add(element); + return false; + } + if (!elementExtracted && classExtracted) { + remainingReferences.add(element); + return false; + } + return true; + }); + if (!extractedReferences.isEmpty()) { + throw new PrepareFailedException( + "Cannot extract method because the selected code fragment uses local classes defined outside of the fragment", + extractedReferences.get(0) + ); + } + if (!remainingReferences.isEmpty()) { + throw new PrepareFailedException( + "Cannot extract method because the selected code fragment defines local classes used outside of the fragment", + remainingReferences.get(0) + ); + } + if (classExtracted) { + for (PsiVariable variable : myControlFlowWrapper.getUsedVariables()) { + if (isDeclaredInside(variable) && !variable.equals(myOutputVariable) && PsiUtil.resolveClassInType(variable.getType()) == localClass) { + throw new PrepareFailedException( + "Cannot extract method because the selected code fragment defines variable of local class type used outside of the fragment", + variable + ); + } + } + } } - return true; - }); - if (!extractedReferences.isEmpty()) { - throw new PrepareFailedException("Cannot extract method because the selected code fragment uses local classes defined outside of the fragment", extractedReferences.get(0)); - } - if (!remainingReferences.isEmpty()) { - throw new PrepareFailedException("Cannot extract method because the selected code fragment defines local classes used outside of the fragment", remainingReferences.get(0)); - } - if (classExtracted) { - for (PsiVariable variable : myControlFlowWrapper.getUsedVariables()) { - if (isDeclaredInside(variable) && !variable.equals(myOutputVariable) && PsiUtil.resolveClassInType(variable.getType()) == localClass) { - throw new PrepareFailedException("Cannot extract method because the selected code fragment defines variable of local class type used outside of the fragment", variable); - } - } - } - } - } - - private boolean isExtractedElement(final PsiElement element) { - boolean isExtracted = false; - for (PsiElement psiElement : myElements) { - if (PsiTreeUtil.isAncestor(psiElement, element, false)) { - isExtracted = true; - break; - } - } - return isExtracted; - } - - - private boolean shouldBeStatic() { - for (PsiElement element : myElements) { - final PsiExpressionStatement statement = PsiTreeUtil.getParentOfType(element, PsiExpressionStatement.class); - if (statement != null && JavaHighlightUtil.isSuperOrThisCall(statement, true, true)) { - return true; - } } - PsiElement codeFragmentMember = myCodeFragmentMember; - while (codeFragmentMember != null && PsiTreeUtil.isAncestor(myTargetClass, codeFragmentMember, true)) { - if (codeFragmentMember instanceof PsiModifierListOwner && ((PsiModifierListOwner) codeFragmentMember).hasModifierProperty(PsiModifier.STATIC)) { - return true; - } - codeFragmentMember = PsiTreeUtil.getParentOfType(codeFragmentMember, PsiModifierListOwner.class, true); - } - return false; - } - - public boolean showDialog(final boolean direct) { - AbstractExtractDialog dialog = createExtractMethodDialog(direct); - dialog.show(); - if (!dialog.isOK()) { - return false; - } - apply(dialog); - return true; - } - - protected void apply(final AbstractExtractDialog dialog) { - myMethodName = dialog.getChosenMethodName(); - myVariableDatum = dialog.getChosenParameters(); - myStatic = isStatic() | dialog.isMakeStatic(); - myIsChainedConstructor = dialog.isChainedConstructor(); - myMethodVisibility = dialog.getVisibility(); - - final PsiType returnType = dialog.getReturnType(); - if (returnType != null) { - myReturnType = returnType; - } - } - - protected AbstractExtractDialog createExtractMethodDialog(final boolean direct) { - final List variables = myInputVariables.getInputVariables(); - myVariableDatum = variables.toArray(new VariableData[variables.size()]); - myNullability = initNullability(); - myArtificialOutputVariable = PsiType.VOID.equals(myReturnType) ? getArtificialOutputVariable() : null; - final PsiType returnType = myArtificialOutputVariable != null ? myArtificialOutputVariable.getType() : myReturnType; - return new ExtractMethodDialog(myProject, myTargetClass, myInputVariables, returnType, getTypeParameterList(), getThrownExceptions(), isStatic(), isCanBeStatic(), myCanBeChainedConstructor, - myRefactoringName, myHelpId, myNullability, myElements) { - @Override - protected boolean areTypesDirected() { - return direct; - } - - @Override - protected String[] suggestMethodNames() { - return suggestInitialMethodName(); - } - - @Override - protected PsiExpression[] findOccurrences() { - return ExtractMethodProcessor.this.findOccurrences(); - } - - @Override - protected boolean isOutputVariable(PsiVariable var) { - return ExtractMethodProcessor.this.isOutputVariable(var); - } - - @Override - protected boolean isVoidReturn() { - return myArtificialOutputVariable != null && !(myArtificialOutputVariable instanceof PsiField); - } - - @Override - protected void checkMethodConflicts(MultiMap conflicts) { - super.checkMethodConflicts(conflicts); - final VariableData[] parameters = getChosenParameters(); - final Map vars = new HashMap<>(); + + private boolean isExtractedElement(final PsiElement element) { + boolean isExtracted = false; + for (PsiElement psiElement : myElements) { + if (PsiTreeUtil.isAncestor(psiElement, element, false)) { + isExtracted = true; + break; + } + } + return isExtracted; + } + + + private boolean shouldBeStatic() { for (PsiElement element : myElements) { - element.accept(new JavaRecursiveElementWalkingVisitor() { + final PsiExpressionStatement statement = PsiTreeUtil.getParentOfType(element, PsiExpressionStatement.class); + if (statement != null && JavaHighlightUtil.isSuperOrThisCall(statement, true, true)) { + return true; + } + } + PsiElement codeFragmentMember = myCodeFragmentMember; + while (codeFragmentMember != null && PsiTreeUtil.isAncestor(myTargetClass, codeFragmentMember, true)) { + if (codeFragmentMember instanceof PsiModifierListOwner && ((PsiModifierListOwner) codeFragmentMember).hasModifierProperty( + PsiModifier.STATIC)) { + return true; + } + codeFragmentMember = PsiTreeUtil.getParentOfType(codeFragmentMember, PsiModifierListOwner.class, true); + } + return false; + } + + public boolean showDialog(final boolean direct) { + AbstractExtractDialog dialog = createExtractMethodDialog(direct); + dialog.show(); + if (!dialog.isOK()) { + return false; + } + apply(dialog); + return true; + } + + protected void apply(final AbstractExtractDialog dialog) { + myMethodName = dialog.getChosenMethodName(); + myVariableDatum = dialog.getChosenParameters(); + myStatic = isStatic() | dialog.isMakeStatic(); + myIsChainedConstructor = dialog.isChainedConstructor(); + myMethodVisibility = dialog.getVisibility(); + + final PsiType returnType = dialog.getReturnType(); + if (returnType != null) { + myReturnType = returnType; + } + } + + protected AbstractExtractDialog createExtractMethodDialog(final boolean direct) { + final List variables = myInputVariables.getInputVariables(); + myVariableDatum = variables.toArray(new VariableData[variables.size()]); + myNullability = initNullability(); + myArtificialOutputVariable = PsiType.VOID.equals(myReturnType) ? getArtificialOutputVariable() : null; + final PsiType returnType = myArtificialOutputVariable != null ? myArtificialOutputVariable.getType() : myReturnType; + return new ExtractMethodDialog( + myProject, + myTargetClass, + myInputVariables, + returnType, + getTypeParameterList(), + getThrownExceptions(), + isStatic(), + isCanBeStatic(), + myCanBeChainedConstructor, + myRefactoringName, + myHelpId, + myNullability, + myElements + ) { @Override - public void visitLocalVariable(PsiLocalVariable variable) { - super.visitLocalVariable(variable); - vars.put(variable.getName(), variable); + protected boolean areTypesDirected() { + return direct; } @Override - public void visitClass(PsiClass aClass) { - } - }); - } - for (VariableData parameter : parameters) { - final String paramName = parameter.name; - final PsiLocalVariable variable = vars.get(paramName); - if (variable != null) { - conflicts.putValue(variable, "Variable with name " + paramName + " is already defined in the selected scope"); - } - } - } - }; - } - - public PsiExpression[] findOccurrences() { - if (myExpression != null) { - return new PsiExpression[]{myExpression}; - } - if (myOutputVariable != null) { - final PsiElement scope = myOutputVariable instanceof PsiLocalVariable localVariable - ? RefactoringUtil.getVariableScope(localVariable) : PsiTreeUtil.findCommonParent(myElements); - return CodeInsightUtil.findReferenceExpressions(scope, myOutputVariable); - } - final List filter = ContainerUtil.filter( - myExitStatements, - statement -> statement instanceof PsiReturnStatement returnStatement && returnStatement.getReturnValue() != null - ); - final List map = ContainerUtil.map(filter, statement -> ((PsiReturnStatement) statement).getReturnValue()); - return map.toArray(new PsiExpression[map.size()]); - } - - private Nullability initNullability() { - if (!PsiUtil.isLanguageLevel5OrHigher(myElements[0]) || PsiUtil.resolveClassInType(myReturnType) == null) { - return null; - } - final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); - final PsiClass nullableAnnotationClass = JavaPsiFacade.getInstance(myProject) - .findClass(manager.getDefaultNullable(), myElements[0].getResolveScope()); - if (nullableAnnotationClass != null) { - final PsiElement elementInCopy = myTargetClass.getContainingFile().copy().findElementAt(myTargetClass.getTextOffset()); - final PsiClass classCopy = PsiTreeUtil.getParentOfType(elementInCopy, PsiClass.class); - if (classCopy == null) { - return null; - } - final PsiMethod emptyMethod = (PsiMethod) classCopy.addAfter(generateEmptyMethod("name", null), classCopy.getLBrace()); - prepareMethodBody(emptyMethod, false); - if (myNotNullConditionalCheck || myNullConditionalCheck) { - return Nullability.NULLABLE; - } - return DfaUtil.inferMethodNullability(emptyMethod); - } - return null; - } - - @RequiredReadAction - protected String[] suggestInitialMethodName() { - if (StringUtil.isEmpty(myInitialMethodName)) { - final Set initialMethodNames = new LinkedHashSet<>(); - final JavaCodeStyleManagerImpl codeStyleManager = (JavaCodeStyleManagerImpl) JavaCodeStyleManager.getInstance(myProject); - if (myExpression != null || !(myReturnType instanceof PsiPrimitiveType)) { - final String[] names = codeStyleManager.suggestVariableName(VariableKind.FIELD, null, myExpression, myReturnType).names; - for (String name : names) { - initialMethodNames.add(codeStyleManager.variableNameToPropertyName(name, VariableKind.FIELD)); - } - } - - if (myOutputVariable != null) { - final VariableKind outKind = codeStyleManager.getVariableKind(myOutputVariable); - final SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName( - VariableKind.FIELD, - codeStyleManager.variableNameToPropertyName(myOutputVariable.getName(), outKind), - null, - myOutputVariable.getType() - ); - for (String name : nameInfo.names) { - initialMethodNames.add(codeStyleManager.variableNameToPropertyName(name, VariableKind.FIELD)); - } - } - - final String nameByComment = getNameByComment(); - final PsiField field = JavaPsiFacade.getElementFactory(myProject).createField("fieldNameToReplace", myReturnType instanceof PsiEllipsisType ? ((PsiEllipsisType) myReturnType).toArrayType - () : myReturnType); - final List getters = new ArrayList<>(ContainerUtil.map(initialMethodNames, propertyName -> { - if (!PsiNameHelper.getInstance(myProject).isIdentifier(propertyName)) { - LOG.info(propertyName + "; " + myExpression); - return null; - } - field.setName(propertyName); - return PropertyUtil.suggestGetterName(field); - })); - ContainerUtil.addIfNotNull(getters, nameByComment); - return ArrayUtil.toStringArray(getters); - } - return new String[]{myInitialMethodName}; - } - - private String getNameByComment() { - PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(myElements[0], PsiWhiteSpace.class); - if (prevSibling instanceof PsiComment comment && comment.getTokenType() == JavaTokenType.END_OF_LINE_COMMENT) { - final String text = StringUtil.decapitalize(StringUtil.capitalizeWords(prevSibling.getText().trim().substring(2), true)) - .replaceAll(" ", ""); - if (PsiNameHelper.getInstance(myProject).isIdentifier(text) && text.length() < 20) { - return text; - } - } - return null; - } - - public boolean isOutputVariable(PsiVariable var) { - return ArrayUtil.find(myOutputVariables, var) != -1; - } - - public boolean showDialog() { - return showDialog(true); - } - - @TestOnly - public void testRun() throws IncorrectOperationException { - testPrepare(); - testNullness(); - ExtractMethodHandler.run(myProject, myEditor, this); - } - - @TestOnly - public void testNullness() { - myNullability = initNullability(); - } - - @TestOnly - public void testPrepare() { - myInputVariables.setFoldingAvailable(myInputVariables.isFoldingSelectedByDefault()); - myMethodName = myInitialMethodName; - myVariableDatum = new VariableData[myInputVariables.getInputVariables().size()]; - for (int i = 0; i < myInputVariables.getInputVariables().size(); i++) { - myVariableDatum[i] = myInputVariables.getInputVariables().get(i); - } - } - - @TestOnly - public void testPrepare(PsiType returnType, boolean makeStatic) throws PrepareFailedException { - if (makeStatic) { - if (!isCanBeStatic()) { - throw new PrepareFailedException("Failed to make static", myElements[0]); - } - myInputVariables.setPassFields(true); - myStatic = true; - } - if (PsiType.VOID.equals(myReturnType)) { - myArtificialOutputVariable = getArtificialOutputVariable(); - } - testPrepare(); - if (returnType != null) { - myReturnType = returnType; - } - } - - @TestOnly - public void doNotPassParameter(int i) { - myVariableDatum[i].passAsParameter = false; - } - - @TestOnly - public void changeParamName(int i, String param) { - myVariableDatum[i].name = param; - } - - /** - * Invoked in command and in atomic action - */ - public void doRefactoring() throws IncorrectOperationException { - initDuplicates(); - - chooseAnchor(); - - LogicalPosition pos1; - if (myEditor != null) { - int col = myEditor.getCaretModel().getLogicalPosition().column; - int line = myEditor.getCaretModel().getLogicalPosition().line; - pos1 = new LogicalPosition(line, col); - LogicalPosition pos = new LogicalPosition(0, 0); - myEditor.getCaretModel().moveToLogicalPosition(pos); - } else { - pos1 = null; - } - - final SearchScope processConflictsScope = myMethodVisibility.equals(PsiModifier.PRIVATE) - ? new LocalSearchScope(myTargetClass) : GlobalSearchScope.projectScope(myProject); - - final Map overloadsResolveMap = new HashMap<>(); - final Runnable collectOverloads = () -> myProject.getApplication().runReadAction(() -> { - Map overloads = - ExtractMethodUtil.encodeOverloadTargets(myTargetClass, processConflictsScope, myMethodName, myCodeFragmentMember); - overloadsResolveMap.putAll(overloads); - }); - final Runnable extract = () -> { - doExtract(); - ExtractMethodUtil.decodeOverloadTargets(overloadsResolveMap, myExtractedMethod, myCodeFragmentMember); - }; - if (myProject.getApplication().isWriteAccessAllowed()) { - collectOverloads.run(); - extract.run(); - } else { - if (!ProgressManager.getInstance() - .runProcessWithProgressSynchronously(collectOverloads, "Collect overloads...", true, myProject)) { - return; - } - myProject.getApplication().runWriteAction(extract); - } - - if (myEditor != null) { - myEditor.getCaretModel().moveToLogicalPosition(pos1); - int offset = myMethodCall.getMethodExpression().getTextRange().getStartOffset(); - myEditor.getCaretModel().moveToOffset(offset); - myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); - myEditor.getSelectionModel().removeSelection(); - } - } - - @Nullable - private DuplicatesFinder initDuplicates() { - List elements = new ArrayList<>(); - for (PsiElement element : myElements) { - if (!(element instanceof PsiWhiteSpace || element instanceof PsiComment)) { - elements.add(element); - } - } - - if (myExpression != null) { - DuplicatesFinder finder = new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(), new ArrayList<>()); - myDuplicates = finder.findDuplicates(myTargetClass); - return finder; - } else if (elements.size() > 0) { - DuplicatesFinder myDuplicatesFinder = new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(), - myOutputVariable != null ? new VariableReturnValue(myOutputVariable) : null, Arrays.asList(myOutputVariables)); - myDuplicates = myDuplicatesFinder.findDuplicates(myTargetClass); - return myDuplicatesFinder; - } else { - myDuplicates = new ArrayList<>(); - } - return null; - } - - @RequiredReadAction - public void doExtract() throws IncorrectOperationException { - PsiMethod newMethod = generateEmptyMethod(); - - myExpression = myInputVariables.replaceWrappedReferences(myElements, myExpression); - renameInputVariables(); - - LOG.assertTrue(myElements[0].isValid()); - - PsiCodeBlock body = newMethod.getBody(); - myMethodCall = generateMethodCall(null, true); - - LOG.assertTrue(myElements[0].isValid()); - - final PsiStatement exitStatementCopy = prepareMethodBody(newMethod, true); - - if (myExpression == null) { - if (myNeedChangeContext && isNeedToChangeCallContext()) { - for (PsiElement element : myElements) { - ChangeContextUtil.encodeContextInfo(element, false); - } - } - - if (myNullConditionalCheck) { - final String varName = myOutputVariable.getName(); - if (isDeclaredInside(myOutputVariable)) { - declareVariableAtMethodCallLocation(varName); - } else { - PsiExpressionStatement assignmentExpression = - (PsiExpressionStatement) myElementFactory.createStatementFromText(varName + "=x;", null); - assignmentExpression = (PsiExpressionStatement) addToMethodCallLocation(assignmentExpression); - myMethodCall = (PsiMethodCallExpression) ((PsiAssignmentExpression) assignmentExpression.getExpression()) - .getRExpression().replace(myMethodCall); - } - declareNecessaryVariablesAfterCall(myOutputVariable); - PsiIfStatement ifStatement; - if (myHasReturnStatementOutput) { - ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText("if (" + varName + "==null) return null;", null); - } else if (myGenerateConditionalExit) { - if (myFirstExitStatementCopy instanceof PsiReturnStatement && ((PsiReturnStatement) myFirstExitStatementCopy).getReturnValue() != null) { - ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText("if (" + varName + "==null) return null;", null); - } else { - ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText("if (" + varName + "==null) " + myFirstExitStatementCopy.getText(), null); - } - } else { - ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText("if (" + varName + "==null) return;", null); - } - ifStatement = (PsiIfStatement) addToMethodCallLocation(ifStatement); - CodeStyleManager.getInstance(myProject).reformat(ifStatement); - } else if (myNotNullConditionalCheck) { - String varName = myOutputVariable != null ? myOutputVariable.getName() : "x"; - varName = declareVariableAtMethodCallLocation( - varName, - myReturnType instanceof PsiPrimitiveType primitiveType ? primitiveType.getBoxedType(myCodeFragmentMember) : myReturnType + protected String[] suggestMethodNames() { + return suggestInitialMethodName(); + } + + @Override + protected PsiExpression[] findOccurrences() { + return ExtractMethodProcessor.this.findOccurrences(); + } + + @Override + protected boolean isOutputVariable(PsiVariable var) { + return ExtractMethodProcessor.this.isOutputVariable(var); + } + + @Override + protected boolean isVoidReturn() { + return myArtificialOutputVariable != null && !(myArtificialOutputVariable instanceof PsiField); + } + + @Override + protected void checkMethodConflicts(MultiMap conflicts) { + super.checkMethodConflicts(conflicts); + final VariableData[] parameters = getChosenParameters(); + final Map vars = new HashMap<>(); + for (PsiElement element : myElements) { + element.accept(new JavaRecursiveElementWalkingVisitor() { + @Override + public void visitLocalVariable(PsiLocalVariable variable) { + super.visitLocalVariable(variable); + vars.put(variable.getName(), variable); + } + + @Override + public void visitClass(PsiClass aClass) { + } + }); + } + for (VariableData parameter : parameters) { + final String paramName = parameter.name; + final PsiLocalVariable variable = vars.get(paramName); + if (variable != null) { + conflicts.putValue( + variable, + LocalizeValue.localizeTODO("Variable with name " + paramName + " is already defined in the selected scope") + ); + } + } + } + }; + } + + public PsiExpression[] findOccurrences() { + if (myExpression != null) { + return new PsiExpression[]{myExpression}; + } + if (myOutputVariable != null) { + final PsiElement scope = myOutputVariable instanceof PsiLocalVariable localVariable + ? RefactoringUtil.getVariableScope(localVariable) : PsiTreeUtil.findCommonParent(myElements); + return CodeInsightUtil.findReferenceExpressions(scope, myOutputVariable); + } + final List filter = ContainerUtil.filter( + myExitStatements, + statement -> statement instanceof PsiReturnStatement returnStatement && returnStatement.getReturnValue() != null ); - addToMethodCallLocation(myElementFactory.createStatementFromText("if (" + varName + " != null) return " + varName + ";", null)); - } else if (myGenerateConditionalExit) { - PsiIfStatement ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText("if (a) b;", null); - ifStatement = (PsiIfStatement) addToMethodCallLocation(ifStatement); - myMethodCall = (PsiMethodCallExpression) ifStatement.getCondition().replace(myMethodCall); - myFirstExitStatementCopy = (PsiStatement) ifStatement.getThenBranch().replace(myFirstExitStatementCopy); - CodeStyleManager.getInstance(myProject).reformat(ifStatement); - } else if (myOutputVariable != null || isArtificialOutputUsed()) { - boolean toDeclare = isArtificialOutputUsed() ? !(myArtificialOutputVariable instanceof PsiField) : isDeclaredInside(myOutputVariable); - String name = isArtificialOutputUsed() ? myArtificialOutputVariable.getName() : myOutputVariable.getName(); - if (!toDeclare) { - PsiExpressionStatement statement = (PsiExpressionStatement) myElementFactory.createStatementFromText(name + "=x;", null); - statement = (PsiExpressionStatement) myStyleManager.reformat(statement); - statement = (PsiExpressionStatement) addToMethodCallLocation(statement); - PsiAssignmentExpression assignment = (PsiAssignmentExpression) statement.getExpression(); - myMethodCall = (PsiMethodCallExpression) assignment.getRExpression().replace(myMethodCall); - } else { - declareVariableAtMethodCallLocation(name); - } - } else if (myHasReturnStatementOutput) { - PsiStatement statement = myElementFactory.createStatementFromText("return x;", null); - statement = (PsiStatement) addToMethodCallLocation(statement); - myMethodCall = (PsiMethodCallExpression) ((PsiReturnStatement) statement).getReturnValue().replace(myMethodCall); - } else { - PsiStatement statement = myElementFactory.createStatementFromText("x();", null); - statement = (PsiStatement) addToMethodCallLocation(statement); - myMethodCall = (PsiMethodCallExpression) ((PsiExpressionStatement) statement).getExpression().replace(myMethodCall); - } - if (myHasReturnStatement && !myHasReturnStatementOutput && !hasNormalExit()) { - PsiStatement statement = myElementFactory.createStatementFromText("return;", null); - addToMethodCallLocation(statement); - } else if (!myGenerateConditionalExit && exitStatementCopy != null) { - addToMethodCallLocation(exitStatementCopy); - } - - if (!myNullConditionalCheck && !myNotNullConditionalCheck) { - declareNecessaryVariablesAfterCall(myOutputVariable); - } - - deleteExtracted(); - } else { - PsiExpression expression2Replace = myExpression; - if (myExpression instanceof PsiAssignmentExpression) { - expression2Replace = ((PsiAssignmentExpression) myExpression).getRExpression(); - } else if (myExpression instanceof PsiPostfixExpression || myExpression instanceof PsiPrefixExpression) { - final IElementType elementType = myExpression instanceof PsiPostfixExpression ? ((PsiPostfixExpression) myExpression).getOperationTokenType() : ((PsiPrefixExpression) myExpression) - .getOperationTokenType(); - if (elementType == JavaTokenType.PLUSPLUS || elementType == JavaTokenType.MINUSMINUS) { - PsiExpression operand = myExpression instanceof PsiPostfixExpression ? ((PsiPostfixExpression) myExpression).getOperand() : ((PsiPrefixExpression) myExpression).getOperand(); - expression2Replace = ((PsiBinaryExpression) myExpression.replace(myElementFactory.createExpressionFromText(operand.getText() + " + x", operand))).getROperand(); - } - - } - myExpression = (PsiExpression) IntroduceVariableBase.replace(expression2Replace, myMethodCall, myProject); - myMethodCall = PsiTreeUtil.getParentOfType(myExpression.findElementAt(myExpression.getText().indexOf(myMethodCall.getText())), PsiMethodCallExpression.class); - declareNecessaryVariablesAfterCall(myOutputVariable); - } - - if (myAnchor instanceof PsiField) { - ((PsiField) myAnchor).normalizeDeclaration(); - } - - adjustFinalParameters(newMethod); - int i = 0; - for (VariableData data : myVariableDatum) { - if (!data.passAsParameter) { - continue; - } - final PsiParameter psiParameter = newMethod.getParameterList().getParameters()[i++]; - final PsiType paramType = psiParameter.getType(); - for (PsiReference reference : ReferencesSearch.search(psiParameter, new LocalSearchScope(body))) { - final PsiElement element = reference.getElement(); - if (element != null) { - final PsiElement parent = element.getParent(); - if (parent instanceof PsiTypeCastExpression) { - final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression) parent; - final PsiTypeElement castType = typeCastExpression.getCastType(); - if (castType != null && Comparing.equal(castType.getType(), paramType)) { - RedundantCastUtil.removeCast(typeCastExpression); - } - } - } - } - } - - if (myNullability != null && - PsiUtil.resolveClassInType(newMethod.getReturnType()) != null && - ProjectPropertiesComponent.getInstance(myProject).getBoolean(ExtractMethodDialog.EXTRACT_METHOD_GENERATE_ANNOTATIONS, true)) { - NullableNotNullManager nullManager = NullableNotNullManager.getInstance(myProject); - switch (myNullability) { - case NOT_NULL: - updateAnnotations(newMethod, nullManager.getNullables(), nullManager.getDefaultNotNull(), nullManager.getNotNulls()); - break; - case NULLABLE: - updateAnnotations(newMethod, nullManager.getNotNulls(), nullManager.getDefaultNullable(), nullManager.getNullables()); - break; - default: - } - } - - myExtractedMethod = (PsiMethod) myTargetClass.addAfter(newMethod, myAnchor); - if (isNeedToChangeCallContext() && myNeedChangeContext) { - ChangeContextUtil.decodeContextInfo(myExtractedMethod, myTargetClass, RefactoringChangeUtil.createThisExpression(myManager, null)); - if (myMethodCall.resolveMethod() != myExtractedMethod) { - final PsiReferenceExpression methodExpression = myMethodCall.getMethodExpression(); - methodExpression.setQualifierExpression(RefactoringChangeUtil.createThisExpression(myManager, myTargetClass)); - } - } - } - - private void updateAnnotations(PsiModifierListOwner owner, List toRemove, String toAdd, List toKeep) { - AddAnnotationPsiFix.removePhysicalAnnotations(owner, ArrayUtil.toStringArray(toRemove)); - PsiModifierList modifierList = owner.getModifierList(); - if (modifierList != null && !AnnotationUtil.isAnnotated(owner, toKeep, CHECK_TYPE)) { - PsiAnnotation annotation = AddAnnotationPsiFix.addPhysicalAnnotationIfAbsent(toAdd, PsiNameValuePair.EMPTY_ARRAY, modifierList); - if (annotation != null) { - JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(annotation); - } - } - } - - @Nullable - @RequiredReadAction - private PsiStatement prepareMethodBody(PsiMethod newMethod, boolean doExtract) { - PsiCodeBlock body = newMethod.getBody(); - if (myExpression != null) { - declareNecessaryVariablesInsideBody(body); - if (myHasExpressionOutput) { - PsiReturnStatement returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return x;", null); - final PsiExpression returnValue = RefactoringUtil.convertInitializerToNormalExpression(myExpression, myForcedReturnType); - returnStatement.getReturnValue().replace(returnValue); - body.add(returnStatement); - } else { - PsiExpressionStatement statement = (PsiExpressionStatement) myElementFactory.createStatementFromText("x;", null); - statement.getExpression().replace(myExpression); - body.add(statement); - } - return null; - } - - final boolean hasNormalExit = hasNormalExit(); - String outVariableName = myOutputVariable != null ? getNewVariableName(myOutputVariable) : null; - PsiReturnStatement returnStatement; - if (myNullConditionalCheck) { - returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return null;", null); - } else if (myOutputVariable != null) { - returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return " + outVariableName + ";", null); - } else if (myGenerateConditionalExit) { - returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return true;", null); - } else { - returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return;", null); - } - - PsiStatement exitStatementCopy = !doExtract || myNotNullConditionalCheck - ? null : myControlFlowWrapper.getExitStatementCopy(returnStatement, myElements); - - declareNecessaryVariablesInsideBody(body); - - body.addRange(myElements[0], myElements[myElements.length - 1]); - if (myNullConditionalCheck) { - body.add(myElementFactory.createStatementFromText("return " + myOutputVariable.getName() + ";", null)); - } else if (myNotNullConditionalCheck) { - body.add(myElementFactory.createStatementFromText("return null;", null)); - } else if (myGenerateConditionalExit) { - body.add(myElementFactory.createStatementFromText("return false;", null)); - } else if (!myHasReturnStatement && hasNormalExit && myOutputVariable != null) { - final PsiReturnStatement insertedReturnStatement = (PsiReturnStatement) body.add(returnStatement); - if (myOutputVariables.length == 1) { - final PsiExpression returnValue = insertedReturnStatement.getReturnValue(); - if (returnValue instanceof PsiReferenceExpression returnReferenceExpression) { - final PsiElement resolved = returnReferenceExpression.resolve(); - if (resolved instanceof PsiLocalVariable localVariable && Comparing.strEqual(localVariable.getName(), outVariableName)) { - final PsiStatement statement = PsiTreeUtil.getPrevSiblingOfType(insertedReturnStatement, PsiStatement.class); - if (statement instanceof PsiDeclarationStatement declarationStatement) { - final PsiElement[] declaredElements = declarationStatement.getDeclaredElements(); - if (ArrayUtil.find(declaredElements, resolved) != -1) { - InlineUtil.inlineVariable(localVariable, localVariable.getInitializer(), returnReferenceExpression); - resolved.delete(); - } - } - } - } - } - } else if (isArtificialOutputUsed()) { - body.add(myElementFactory.createStatementFromText("return " + myArtificialOutputVariable.getName() + ";", null)); - } - return exitStatementCopy; - } - - private boolean isArtificialOutputUsed() { - return myArtificialOutputVariable != null && !PsiType.VOID.equals(myReturnType) && !myIsChainedConstructor; - } - - private boolean hasNormalExit() { - boolean hasNormalExit = false; - PsiElement lastElement = myElements[myElements.length - 1]; - if (!(lastElement instanceof PsiReturnStatement || lastElement instanceof PsiBreakStatement || - lastElement instanceof PsiContinueStatement)) { - hasNormalExit = true; - } - return hasNormalExit; - } - - protected boolean isNeedToChangeCallContext() { - return true; - } - - private void declareVariableAtMethodCallLocation(String name) { - declareVariableAtMethodCallLocation(name, myReturnType); - } - - private String declareVariableAtMethodCallLocation(String name, PsiType type) { - if (myControlFlowWrapper.getOutputVariables(false).length == 0) { - PsiElement lastStatement = PsiTreeUtil.getNextSiblingOfType( - myEnclosingBlockStatement != null ? myEnclosingBlockStatement : myElements[myElements.length - 1], - PsiStatement.class - ); - if (lastStatement != null) { - name = JavaCodeStyleManager.getInstance(myProject).suggestUniqueVariableName(name, lastStatement, true); - } - } - PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, type, myMethodCall); - statement = (PsiDeclarationStatement) addToMethodCallLocation(statement); - PsiVariable var = (PsiVariable) statement.getDeclaredElements()[0]; - myMethodCall = (PsiMethodCallExpression) var.getInitializer(); - if (myOutputVariable != null) { - var.getModifierList().replace(myOutputVariable.getModifierList()); - } - return name; - } - - private void adjustFinalParameters(final PsiMethod method) throws IncorrectOperationException { - final IncorrectOperationException[] exc = new IncorrectOperationException[1]; - exc[0] = null; - final PsiParameter[] parameters = method.getParameterList().getParameters(); - if (parameters.length > 0) { - if (CodeStyleSettingsManager.getSettings(myProject).GENERATE_FINAL_PARAMETERS) { - method.accept(new JavaRecursiveElementVisitor() { - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - final PsiElement resolved = expression.resolve(); - if (resolved != null) { - final int index = ArrayUtil.find(parameters, resolved); - if (index >= 0) { - final PsiParameter param = parameters[index]; - if (param.hasModifierProperty(PsiModifier.FINAL) && PsiUtil.isAccessedForWriting(expression)) { - try { - PsiUtil.setModifierProperty(param, PsiModifier.FINAL, false); - } catch (IncorrectOperationException e) { - exc[0] = e; - } + final List map = ContainerUtil.map(filter, statement -> ((PsiReturnStatement) statement).getReturnValue()); + return map.toArray(new PsiExpression[map.size()]); + } + + private Nullability initNullability() { + if (!PsiUtil.isLanguageLevel5OrHigher(myElements[0]) || PsiUtil.resolveClassInType(myReturnType) == null) { + return null; + } + final NullableNotNullManager manager = NullableNotNullManager.getInstance(myProject); + final PsiClass nullableAnnotationClass = JavaPsiFacade.getInstance(myProject) + .findClass(manager.getDefaultNullable(), myElements[0].getResolveScope()); + if (nullableAnnotationClass != null) { + final PsiElement elementInCopy = myTargetClass.getContainingFile().copy().findElementAt(myTargetClass.getTextOffset()); + final PsiClass classCopy = PsiTreeUtil.getParentOfType(elementInCopy, PsiClass.class); + if (classCopy == null) { + return null; + } + final PsiMethod emptyMethod = (PsiMethod) classCopy.addAfter(generateEmptyMethod("name", null), classCopy.getLBrace()); + prepareMethodBody(emptyMethod, false); + if (myNotNullConditionalCheck || myNullConditionalCheck) { + return Nullability.NULLABLE; + } + return DfaUtil.inferMethodNullability(emptyMethod); + } + return null; + } + + @RequiredReadAction + protected String[] suggestInitialMethodName() { + if (StringUtil.isEmpty(myInitialMethodName)) { + final Set initialMethodNames = new LinkedHashSet<>(); + final JavaCodeStyleManagerImpl codeStyleManager = (JavaCodeStyleManagerImpl) JavaCodeStyleManager.getInstance(myProject); + if (myExpression != null || !(myReturnType instanceof PsiPrimitiveType)) { + final String[] names = codeStyleManager.suggestVariableName(VariableKind.FIELD, null, myExpression, myReturnType).names; + for (String name : names) { + initialMethodNames.add(codeStyleManager.variableNameToPropertyName(name, VariableKind.FIELD)); } - } } - super.visitReferenceExpression(expression); - } - }); - } else { - method.accept(new JavaRecursiveElementVisitor() { - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - final PsiElement resolved = expression.resolve(); - final int index = ArrayUtil.find(parameters, resolved); - if (index >= 0) { - final PsiParameter param = parameters[index]; - if (!param.hasModifierProperty(PsiModifier.FINAL) && RefactoringUtil.isInsideAnonymousOrLocal(expression, method)) { - try { - PsiUtil.setModifierProperty(param, PsiModifier.FINAL, true); - } catch (IncorrectOperationException e) { - exc[0] = e; + + if (myOutputVariable != null) { + final VariableKind outKind = codeStyleManager.getVariableKind(myOutputVariable); + final SuggestedNameInfo nameInfo = codeStyleManager.suggestVariableName( + VariableKind.FIELD, + codeStyleManager.variableNameToPropertyName(myOutputVariable.getName(), outKind), + null, + myOutputVariable.getType() + ); + for (String name : nameInfo.names) { + initialMethodNames.add(codeStyleManager.variableNameToPropertyName(name, VariableKind.FIELD)); + } + } + + final String nameByComment = getNameByComment(); + final PsiField field = JavaPsiFacade.getElementFactory(myProject) + .createField("fieldNameToReplace", myReturnType instanceof PsiEllipsisType ? ((PsiEllipsisType) myReturnType).toArrayType + () : myReturnType); + final List getters = new ArrayList<>(ContainerUtil.map(initialMethodNames, propertyName -> { + if (!PsiNameHelper.getInstance(myProject).isIdentifier(propertyName)) { + LOG.info(propertyName + "; " + myExpression); + return null; } - } + field.setName(propertyName); + return PropertyUtil.suggestGetterName(field); + })); + ContainerUtil.addIfNotNull(getters, nameByComment); + return ArrayUtil.toStringArray(getters); + } + return new String[]{myInitialMethodName}; + } + + private String getNameByComment() { + PsiElement prevSibling = PsiTreeUtil.skipSiblingsBackward(myElements[0], PsiWhiteSpace.class); + if (prevSibling instanceof PsiComment comment && comment.getTokenType() == JavaTokenType.END_OF_LINE_COMMENT) { + final String text = StringUtil.decapitalize(StringUtil.capitalizeWords(prevSibling.getText().trim().substring(2), true)) + .replaceAll(" ", ""); + if (PsiNameHelper.getInstance(myProject).isIdentifier(text) && text.length() < 20) { + return text; } - super.visitReferenceExpression(expression); - } + } + return null; + } + + public boolean isOutputVariable(PsiVariable var) { + return ArrayUtil.find(myOutputVariables, var) != -1; + } + + public boolean showDialog() { + return showDialog(true); + } + + @TestOnly + public void testRun() throws IncorrectOperationException { + testPrepare(); + testNullness(); + ExtractMethodHandler.run(myProject, myEditor, this); + } + + @TestOnly + public void testNullness() { + myNullability = initNullability(); + } + + @TestOnly + public void testPrepare() { + myInputVariables.setFoldingAvailable(myInputVariables.isFoldingSelectedByDefault()); + myMethodName = myInitialMethodName; + myVariableDatum = new VariableData[myInputVariables.getInputVariables().size()]; + for (int i = 0; i < myInputVariables.getInputVariables().size(); i++) { + myVariableDatum[i] = myInputVariables.getInputVariables().get(i); + } + } + + @TestOnly + public void testPrepare(PsiType returnType, boolean makeStatic) throws PrepareFailedException { + if (makeStatic) { + if (!isCanBeStatic()) { + throw new PrepareFailedException("Failed to make static", myElements[0]); + } + myInputVariables.setPassFields(true); + myStatic = true; + } + if (PsiType.VOID.equals(myReturnType)) { + myArtificialOutputVariable = getArtificialOutputVariable(); + } + testPrepare(); + if (returnType != null) { + myReturnType = returnType; + } + } + + @TestOnly + public void doNotPassParameter(int i) { + myVariableDatum[i].passAsParameter = false; + } + + @TestOnly + public void changeParamName(int i, String param) { + myVariableDatum[i].name = param; + } + + /** + * Invoked in command and in atomic action + */ + public void doRefactoring() throws IncorrectOperationException { + initDuplicates(); + + chooseAnchor(); + + LogicalPosition pos1; + if (myEditor != null) { + int col = myEditor.getCaretModel().getLogicalPosition().column; + int line = myEditor.getCaretModel().getLogicalPosition().line; + pos1 = new LogicalPosition(line, col); + LogicalPosition pos = new LogicalPosition(0, 0); + myEditor.getCaretModel().moveToLogicalPosition(pos); + } + else { + pos1 = null; + } + + final SearchScope processConflictsScope = myMethodVisibility.equals(PsiModifier.PRIVATE) + ? new LocalSearchScope(myTargetClass) : GlobalSearchScope.projectScope(myProject); + + final Map overloadsResolveMap = new HashMap<>(); + final Runnable collectOverloads = () -> myProject.getApplication().runReadAction(() -> { + Map overloads = + ExtractMethodUtil.encodeOverloadTargets(myTargetClass, processConflictsScope, myMethodName, myCodeFragmentMember); + overloadsResolveMap.putAll(overloads); }); - } - if (exc[0] != null) { - throw exc[0]; - } - } - } - - @Override - public List getDuplicates() { - if (myIsChainedConstructor) { - return filterChainedConstructorDuplicates(myDuplicates); - } - return myDuplicates; - } - - private static List filterChainedConstructorDuplicates(final List duplicates) { - List result = new ArrayList(); - for (Match duplicate : duplicates) { - final PsiElement matchStart = duplicate.getMatchStart(); - final PsiMethod method = PsiTreeUtil.getParentOfType(matchStart, PsiMethod.class); - if (method != null && method.isConstructor()) { - final PsiCodeBlock body = method.getBody(); - if (body != null) { - final PsiStatement[] psiStatements = body.getStatements(); - if (psiStatements.length > 0 && matchStart == psiStatements[0]) { - result.add(duplicate); - } - } - } - } - return result; - } - - @Override - public PsiElement processMatch(Match match) throws IncorrectOperationException { - MatchUtil.changeSignature(match, myExtractedMethod); - if (RefactoringUtil.isInStaticContext(match.getMatchStart(), myExtractedMethod.getContainingClass())) { - PsiUtil.setModifierProperty(myExtractedMethod, PsiModifier.STATIC, true); - } - final PsiMethodCallExpression methodCallExpression = generateMethodCall(match.getInstanceExpression(), false); - - ArrayList datas = new ArrayList(); - for (final VariableData variableData : myVariableDatum) { - if (variableData.passAsParameter) { - datas.add(variableData); - } - } - final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject); - for (VariableData data : datas) { - final List parameterValue = match.getParameterValues(data.variable); - if (parameterValue != null) { - for (PsiElement val : parameterValue) { - if (val instanceof PsiExpression) { - final PsiType exprType = ((PsiExpression) val).getType(); - if (exprType != null && !TypeConversionUtil.isAssignable(data.type, exprType)) { - final PsiTypeCastExpression cast = (PsiTypeCastExpression) elementFactory.createExpressionFromText("(A)a", val); - cast.getCastType().replace(elementFactory.createTypeElement(data.type)); - cast.getOperand().replace(val.copy()); - val = cast; - } - } - methodCallExpression.getArgumentList().add(val); - } - } else { - methodCallExpression.getArgumentList() - .add(myElementFactory.createExpressionFromText(data.variable.getName(), methodCallExpression)); - } - } - return match.replace(myExtractedMethod, methodCallExpression, myOutputVariable); - } - - protected void deleteExtracted() throws IncorrectOperationException { - if (myEnclosingBlockStatement == null) { - myElements[0].getParent().deleteChildRange(myElements[0], myElements[myElements.length - 1]); - } else { - myEnclosingBlockStatement.delete(); - } - } - - protected PsiElement addToMethodCallLocation(PsiStatement statement) throws IncorrectOperationException { - if (myEnclosingBlockStatement == null) { - PsiElement containingStatement = myElements[0] instanceof PsiComment ? myElements[0] - : PsiTreeUtil.getParentOfType(myExpression != null ? myExpression : myElements[0], PsiStatement.class, false); - if (containingStatement == null) { - containingStatement = PsiTreeUtil.getParentOfType(myExpression != null ? myExpression : myElements[0], PsiComment.class, false); - } - - return containingStatement.getParent().addBefore(statement, containingStatement); - } else { - return myEnclosingBlockStatement.getParent().addBefore(statement, myEnclosingBlockStatement); - } - } - - private void renameInputVariables() throws IncorrectOperationException { - //when multiple input variables should have the same name, unique names are generated - //without reverse, the second rename would rename variable without a prefix into second one though it was already renamed - for (int i = myVariableDatum.length - 1; i >= 0; i--) { - VariableData data = myVariableDatum[i]; - PsiVariable variable = data.variable; - if (!data.name.equals(variable.getName())) { + final Runnable extract = () -> { + doExtract(); + ExtractMethodUtil.decodeOverloadTargets(overloadsResolveMap, myExtractedMethod, myCodeFragmentMember); + }; + if (myProject.getApplication().isWriteAccessAllowed()) { + collectOverloads.run(); + extract.run(); + } + else { + if (!ProgressManager.getInstance() + .runProcessWithProgressSynchronously(collectOverloads, "Collect overloads...", true, myProject)) { + return; + } + myProject.getApplication().runWriteAction(extract); + } + + if (myEditor != null) { + myEditor.getCaretModel().moveToLogicalPosition(pos1); + int offset = myMethodCall.getMethodExpression().getTextRange().getStartOffset(); + myEditor.getCaretModel().moveToOffset(offset); + myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + myEditor.getSelectionModel().removeSelection(); + } + } + + @Nullable + private DuplicatesFinder initDuplicates() { + List elements = new ArrayList<>(); for (PsiElement element : myElements) { - RefactoringUtil.renameVariableReferences(variable, data.name, new LocalSearchScope(element)); - } - } - } - } - - public PsiClass getTargetClass() { - return myTargetClass; - } - - public PsiType getReturnType() { - return myReturnType; - } - - private PsiMethod generateEmptyMethod() throws IncorrectOperationException { - return generateEmptyMethod(myMethodName, null); - } - - public PsiMethod generateEmptyMethod(String methodName, PsiElement context) throws IncorrectOperationException { - PsiMethod newMethod; - if (myIsChainedConstructor) { - newMethod = myElementFactory.createConstructor(); - } else { - newMethod = context != null ? myElementFactory.createMethod(methodName, myReturnType, context) - : myElementFactory.createMethod(methodName, myReturnType); - PsiUtil.setModifierProperty(newMethod, PsiModifier.STATIC, isStatic()); - } - PsiUtil.setModifierProperty(newMethod, myMethodVisibility, true); - if (getTypeParameterList() != null) { - newMethod.getTypeParameterList().replace(getTypeParameterList()); - } - PsiCodeBlock body = newMethod.getBody(); - LOG.assertTrue(body != null); - - boolean isFinal = CodeStyleSettingsManager.getSettings(myProject).GENERATE_FINAL_PARAMETERS; - PsiParameterList list = newMethod.getParameterList(); - for (VariableData data : myVariableDatum) { - if (data.passAsParameter) { - PsiParameter parm = myElementFactory.createParameter(data.name, data.type); - copyParamAnnotations(parm); - if (isFinal) { - PsiUtil.setModifierProperty(parm, PsiModifier.FINAL, true); - } - list.add(parm); - } else { - @NonNls StringBuilder buffer = new StringBuilder(); - if (isFinal) { - buffer.append("final "); + if (!(element instanceof PsiWhiteSpace || element instanceof PsiComment)) { + elements.add(element); + } + } + + if (myExpression != null) { + DuplicatesFinder finder = + new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(), new ArrayList<>()); + myDuplicates = finder.findDuplicates(myTargetClass); + return finder; + } + else if (elements.size() > 0) { + DuplicatesFinder myDuplicatesFinder = new DuplicatesFinder(PsiUtilCore.toPsiElementArray(elements), myInputVariables.copy(), + myOutputVariable != null ? new VariableReturnValue(myOutputVariable) : null, Arrays.asList(myOutputVariables) + ); + myDuplicates = myDuplicatesFinder.findDuplicates(myTargetClass); + return myDuplicatesFinder; + } + else { + myDuplicates = new ArrayList<>(); + } + return null; + } + + @RequiredReadAction + public void doExtract() throws IncorrectOperationException { + PsiMethod newMethod = generateEmptyMethod(); + + myExpression = myInputVariables.replaceWrappedReferences(myElements, myExpression); + renameInputVariables(); + + LOG.assertTrue(myElements[0].isValid()); + + PsiCodeBlock body = newMethod.getBody(); + myMethodCall = generateMethodCall(null, true); + + LOG.assertTrue(myElements[0].isValid()); + + final PsiStatement exitStatementCopy = prepareMethodBody(newMethod, true); + + if (myExpression == null) { + if (myNeedChangeContext && isNeedToChangeCallContext()) { + for (PsiElement element : myElements) { + ChangeContextUtil.encodeContextInfo(element, false); + } + } + + if (myNullConditionalCheck) { + final String varName = myOutputVariable.getName(); + if (isDeclaredInside(myOutputVariable)) { + declareVariableAtMethodCallLocation(varName); + } + else { + PsiExpressionStatement assignmentExpression = + (PsiExpressionStatement) myElementFactory.createStatementFromText(varName + "=x;", null); + assignmentExpression = (PsiExpressionStatement) addToMethodCallLocation(assignmentExpression); + myMethodCall = (PsiMethodCallExpression) ((PsiAssignmentExpression) assignmentExpression.getExpression()) + .getRExpression().replace(myMethodCall); + } + declareNecessaryVariablesAfterCall(myOutputVariable); + PsiIfStatement ifStatement; + if (myHasReturnStatementOutput) { + ifStatement = + (PsiIfStatement) myElementFactory.createStatementFromText("if (" + varName + "==null) return null;", null); + } + else if (myGenerateConditionalExit) { + if (myFirstExitStatementCopy instanceof PsiReturnStatement && ((PsiReturnStatement) myFirstExitStatementCopy).getReturnValue() != null) { + ifStatement = + (PsiIfStatement) myElementFactory.createStatementFromText("if (" + varName + "==null) return null;", null); + } + else { + ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText( + "if (" + varName + "==null) " + myFirstExitStatementCopy.getText(), + null + ); + } + } + else { + ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText("if (" + varName + "==null) return;", null); + } + ifStatement = (PsiIfStatement) addToMethodCallLocation(ifStatement); + CodeStyleManager.getInstance(myProject).reformat(ifStatement); + } + else if (myNotNullConditionalCheck) { + String varName = myOutputVariable != null ? myOutputVariable.getName() : "x"; + varName = declareVariableAtMethodCallLocation( + varName, + myReturnType instanceof PsiPrimitiveType primitiveType ? primitiveType.getBoxedType(myCodeFragmentMember) : myReturnType + ); + addToMethodCallLocation(myElementFactory.createStatementFromText( + "if (" + varName + " != null) return " + varName + ";", + null + )); + } + else if (myGenerateConditionalExit) { + PsiIfStatement ifStatement = (PsiIfStatement) myElementFactory.createStatementFromText("if (a) b;", null); + ifStatement = (PsiIfStatement) addToMethodCallLocation(ifStatement); + myMethodCall = (PsiMethodCallExpression) ifStatement.getCondition().replace(myMethodCall); + myFirstExitStatementCopy = (PsiStatement) ifStatement.getThenBranch().replace(myFirstExitStatementCopy); + CodeStyleManager.getInstance(myProject).reformat(ifStatement); + } + else if (myOutputVariable != null || isArtificialOutputUsed()) { + boolean toDeclare = + isArtificialOutputUsed() ? !(myArtificialOutputVariable instanceof PsiField) : isDeclaredInside(myOutputVariable); + String name = isArtificialOutputUsed() ? myArtificialOutputVariable.getName() : myOutputVariable.getName(); + if (!toDeclare) { + PsiExpressionStatement statement = + (PsiExpressionStatement) myElementFactory.createStatementFromText(name + "=x;", null); + statement = (PsiExpressionStatement) myStyleManager.reformat(statement); + statement = (PsiExpressionStatement) addToMethodCallLocation(statement); + PsiAssignmentExpression assignment = (PsiAssignmentExpression) statement.getExpression(); + myMethodCall = (PsiMethodCallExpression) assignment.getRExpression().replace(myMethodCall); + } + else { + declareVariableAtMethodCallLocation(name); + } + } + else if (myHasReturnStatementOutput) { + PsiStatement statement = myElementFactory.createStatementFromText("return x;", null); + statement = (PsiStatement) addToMethodCallLocation(statement); + myMethodCall = (PsiMethodCallExpression) ((PsiReturnStatement) statement).getReturnValue().replace(myMethodCall); + } + else { + PsiStatement statement = myElementFactory.createStatementFromText("x();", null); + statement = (PsiStatement) addToMethodCallLocation(statement); + myMethodCall = (PsiMethodCallExpression) ((PsiExpressionStatement) statement).getExpression().replace(myMethodCall); + } + if (myHasReturnStatement && !myHasReturnStatementOutput && !hasNormalExit()) { + PsiStatement statement = myElementFactory.createStatementFromText("return;", null); + addToMethodCallLocation(statement); + } + else if (!myGenerateConditionalExit && exitStatementCopy != null) { + addToMethodCallLocation(exitStatementCopy); + } + + if (!myNullConditionalCheck && !myNotNullConditionalCheck) { + declareNecessaryVariablesAfterCall(myOutputVariable); + } + + deleteExtracted(); + } + else { + PsiExpression expression2Replace = myExpression; + if (myExpression instanceof PsiAssignmentExpression) { + expression2Replace = ((PsiAssignmentExpression) myExpression).getRExpression(); + } + else if (myExpression instanceof PsiPostfixExpression || myExpression instanceof PsiPrefixExpression) { + final IElementType elementType = + myExpression instanceof PsiPostfixExpression ? ((PsiPostfixExpression) myExpression).getOperationTokenType() : ((PsiPrefixExpression) myExpression) + .getOperationTokenType(); + if (elementType == JavaTokenType.PLUSPLUS || elementType == JavaTokenType.MINUSMINUS) { + PsiExpression operand = + myExpression instanceof PsiPostfixExpression ? ((PsiPostfixExpression) myExpression).getOperand() : ((PsiPrefixExpression) myExpression).getOperand(); + expression2Replace = ((PsiBinaryExpression) myExpression.replace(myElementFactory.createExpressionFromText( + operand.getText() + " + x", + operand + ))).getROperand(); + } + + } + myExpression = (PsiExpression) IntroduceVariableBase.replace(expression2Replace, myMethodCall, myProject); + myMethodCall = PsiTreeUtil.getParentOfType( + myExpression.findElementAt(myExpression.getText().indexOf(myMethodCall.getText())), + PsiMethodCallExpression.class + ); + declareNecessaryVariablesAfterCall(myOutputVariable); + } + + if (myAnchor instanceof PsiField) { + ((PsiField) myAnchor).normalizeDeclaration(); } - buffer.append("int "); - buffer.append(data.name); - buffer.append("=;"); - String text = buffer.toString(); - PsiDeclarationStatement declaration = (PsiDeclarationStatement) myElementFactory.createStatementFromText(text, null); - declaration = (PsiDeclarationStatement) myStyleManager.reformat(declaration); - final PsiTypeElement typeElement = myElementFactory.createTypeElement(data.type); - ((PsiVariable) declaration.getDeclaredElements()[0]).getTypeElement().replace(typeElement); - body.add(declaration); - } - } - - PsiReferenceList throwsList = newMethod.getThrowsList(); - for (PsiClassType exception : getThrownExceptions()) { - throwsList.add(JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory().createReferenceElementByType(exception)); - } - - if (myTargetClass.isInterface() && PsiUtil.isLanguageLevel8OrHigher(myTargetClass)) { - final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiMethod.class, false); - if (containingMethod != null && containingMethod.hasModifierProperty(PsiModifier.DEFAULT)) { - PsiUtil.setModifierProperty(newMethod, PsiModifier.DEFAULT, true); - } - } - return (PsiMethod) myStyleManager.reformat(newMethod); - } - - private void copyParamAnnotations(PsiParameter parm) { - final PsiVariable variable = PsiResolveHelper.getInstance(myProject).resolveReferencedVariable(parm.getName(), myElements[0]); - if (variable instanceof PsiParameter) { - final PsiModifierList modifierList = variable.getModifierList(); - if (modifierList != null) { - for (PsiAnnotation annotation : modifierList.getAnnotations()) { - if (SuppressWarnings.class.getName().equals(annotation.getQualifiedName())) { - continue; - } - final PsiModifierList parmModifierList = parm.getModifierList(); - LOG.assertTrue(parmModifierList != null, parm); - parmModifierList.add(annotation); - } - } - } - } - - @Nonnull - protected PsiMethodCallExpression generateMethodCall(PsiExpression instanceQualifier, final boolean generateArgs) - throws IncorrectOperationException { - @NonNls StringBuilder buffer = new StringBuilder(); - - final boolean skipInstanceQualifier; - if (myIsChainedConstructor) { - skipInstanceQualifier = true; - buffer.append(PsiKeyword.THIS); - } else { - skipInstanceQualifier = instanceQualifier == null || instanceQualifier instanceof PsiThisExpression; - if (skipInstanceQualifier) { + adjustFinalParameters(newMethod); + int i = 0; + for (VariableData data : myVariableDatum) { + if (!data.passAsParameter) { + continue; + } + final PsiParameter psiParameter = newMethod.getParameterList().getParameters()[i++]; + final PsiType paramType = psiParameter.getType(); + for (PsiReference reference : ReferencesSearch.search(psiParameter, new LocalSearchScope(body))) { + final PsiElement element = reference.getElement(); + if (element != null) { + final PsiElement parent = element.getParent(); + if (parent instanceof PsiTypeCastExpression) { + final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression) parent; + final PsiTypeElement castType = typeCastExpression.getCastType(); + if (castType != null && Comparing.equal(castType.getType(), paramType)) { + RedundantCastUtil.removeCast(typeCastExpression); + } + } + } + } + } + + if (myNullability != null && + PsiUtil.resolveClassInType(newMethod.getReturnType()) != null && + ProjectPropertiesComponent.getInstance(myProject).getBoolean(ExtractMethodDialog.EXTRACT_METHOD_GENERATE_ANNOTATIONS, true)) { + NullableNotNullManager nullManager = NullableNotNullManager.getInstance(myProject); + switch (myNullability) { + case NOT_NULL: + updateAnnotations(newMethod, nullManager.getNullables(), nullManager.getDefaultNotNull(), nullManager.getNotNulls()); + break; + case NULLABLE: + updateAnnotations(newMethod, nullManager.getNotNulls(), nullManager.getDefaultNullable(), nullManager.getNullables()); + break; + default: + } + } + + myExtractedMethod = (PsiMethod) myTargetClass.addAfter(newMethod, myAnchor); if (isNeedToChangeCallContext() && myNeedChangeContext) { - boolean needsThisQualifier = false; - PsiElement parent = myCodeFragmentMember; - while (!myTargetClass.equals(parent)) { - if (parent instanceof PsiMethod) { - String methodName = ((PsiMethod) parent).getName(); - if (methodName.equals(myMethodName)) { - needsThisQualifier = true; - break; - } - } - parent = parent.getParent(); - } - if (needsThisQualifier) { - buffer.append(myTargetClass.getName()).append(".this."); - } - } - } else { - buffer.append("qqq."); - } - - buffer.append(myMethodName); - } - buffer.append("("); - if (generateArgs) { - int count = 0; - for (VariableData data : myVariableDatum) { - if (data.passAsParameter) { - if (count > 0) { - buffer.append(","); - } - myInputVariables.appendCallArguments(data, buffer); - count++; - } - } - } - buffer.append(")"); - String text = buffer.toString(); - - PsiMethodCallExpression expr = (PsiMethodCallExpression) myElementFactory.createExpressionFromText(text, null); - expr = (PsiMethodCallExpression) myStyleManager.reformat(expr); - if (!skipInstanceQualifier) { - PsiExpression qualifierExpression = expr.getMethodExpression().getQualifierExpression(); - LOG.assertTrue(qualifierExpression != null); - qualifierExpression.replace(instanceQualifier); - } - return (PsiMethodCallExpression) JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(expr); - } - - private boolean chooseTargetClass(PsiElement codeFragment, final Consumer extractPass) throws PrepareFailedException { - final List inputVariables = myControlFlowWrapper.getInputVariables(codeFragment, myElements, myOutputVariables); - - myNeedChangeContext = false; - myTargetClass = myCodeFragmentMember instanceof PsiMember ? ((PsiMember) myCodeFragmentMember).getContainingClass() : PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiClass.class); - if (!shouldAcceptCurrentTarget(extractPass, myTargetClass)) { - - final LinkedHashMap> classes = new LinkedHashMap<>(); - final PsiElementProcessor processor = selectedClass -> { - AnonymousTargetClassPreselectionUtil.rememberSelection(selectedClass, myTargetClass); - final List array = classes.get(selectedClass); - myNeedChangeContext = myTargetClass != selectedClass; - myTargetClass = selectedClass; - if (array != null) { - for (PsiVariable variable : array) { - if (!inputVariables.contains(variable)) { - inputVariables.addAll(array); - } - } + ChangeContextUtil.decodeContextInfo( + myExtractedMethod, + myTargetClass, + RefactoringChangeUtil.createThisExpression(myManager, null) + ); + if (myMethodCall.resolveMethod() != myExtractedMethod) { + final PsiReferenceExpression methodExpression = myMethodCall.getMethodExpression(); + methodExpression.setQualifierExpression(RefactoringChangeUtil.createThisExpression(myManager, myTargetClass)); + } } - try { - return applyChosenClassAndExtract(inputVariables, extractPass); - } catch (PrepareFailedException e) { - if (myShowErrorDialogs) { - CommonRefactoringUtil.showErrorHint(myProject, myEditor, e.getMessage(), ExtractMethodHandler.REFACTORING_NAME, HelpID.EXTRACT_METHOD); - ExtractMethodHandler.highlightPrepareError(e, e.getFile(), myEditor, myProject); - } - return false; - } - }; - - classes.put(myTargetClass, null); - PsiElement target = myTargetClass.getParent(); - PsiElement targetMember = myTargetClass; - while (true) { - if (target instanceof PsiFile) { - break; - } - if (target instanceof PsiClass psiClass) { - boolean success = true; - final List array = new ArrayList<>(); - for (PsiElement el : myElements) { - if (!ControlFlowUtil.collectOuterLocals(array, el, myCodeFragmentMember, targetMember)) { - success = false; - break; - } - } - if (success) { - classes.put(psiClass, array); - if (shouldAcceptCurrentTarget(extractPass, target)) { - return processor.execute(psiClass); - } - } - } - targetMember = target; - target = target.getParent(); - } - - if (classes.size() > 1) { - final PsiClass[] psiClasses = classes.keySet().toArray(new PsiClass[classes.size()]); - final PsiClass preselection = AnonymousTargetClassPreselectionUtil.getPreselection(classes.keySet(), psiClasses[0]); - JBPopup popup = PopupNavigationUtil.getPsiElementPopup( - psiClasses, - new PsiClassListCellRenderer(), - "Choose Destination Class", - processor, - preselection - ); - EditorPopupHelper.getInstance().showPopupInBestPositionFor(myEditor, popup); + } + + private void updateAnnotations(PsiModifierListOwner owner, List toRemove, String toAdd, List toKeep) { + AddAnnotationPsiFix.removePhysicalAnnotations(owner, ArrayUtil.toStringArray(toRemove)); + PsiModifierList modifierList = owner.getModifierList(); + if (modifierList != null && !AnnotationUtil.isAnnotated(owner, toKeep, CHECK_TYPE)) { + PsiAnnotation annotation = AddAnnotationPsiFix.addPhysicalAnnotationIfAbsent(toAdd, PsiNameValuePair.EMPTY_ARRAY, modifierList); + if (annotation != null) { + JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(annotation); + } + } + } + + @Nullable + @RequiredReadAction + private PsiStatement prepareMethodBody(PsiMethod newMethod, boolean doExtract) { + PsiCodeBlock body = newMethod.getBody(); + if (myExpression != null) { + declareNecessaryVariablesInsideBody(body); + if (myHasExpressionOutput) { + PsiReturnStatement returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return x;", null); + final PsiExpression returnValue = RefactoringUtil.convertInitializerToNormalExpression(myExpression, myForcedReturnType); + returnStatement.getReturnValue().replace(returnValue); + body.add(returnStatement); + } + else { + PsiExpressionStatement statement = (PsiExpressionStatement) myElementFactory.createStatementFromText("x;", null); + statement.getExpression().replace(myExpression); + body.add(statement); + } + return null; + } + + final boolean hasNormalExit = hasNormalExit(); + String outVariableName = myOutputVariable != null ? getNewVariableName(myOutputVariable) : null; + PsiReturnStatement returnStatement; + if (myNullConditionalCheck) { + returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return null;", null); + } + else if (myOutputVariable != null) { + returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return " + outVariableName + ";", null); + } + else if (myGenerateConditionalExit) { + returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return true;", null); + } + else { + returnStatement = (PsiReturnStatement) myElementFactory.createStatementFromText("return;", null); + } + + PsiStatement exitStatementCopy = !doExtract || myNotNullConditionalCheck + ? null : myControlFlowWrapper.getExitStatementCopy(returnStatement, myElements); + + declareNecessaryVariablesInsideBody(body); + + body.addRange(myElements[0], myElements[myElements.length - 1]); + if (myNullConditionalCheck) { + body.add(myElementFactory.createStatementFromText("return " + myOutputVariable.getName() + ";", null)); + } + else if (myNotNullConditionalCheck) { + body.add(myElementFactory.createStatementFromText("return null;", null)); + } + else if (myGenerateConditionalExit) { + body.add(myElementFactory.createStatementFromText("return false;", null)); + } + else if (!myHasReturnStatement && hasNormalExit && myOutputVariable != null) { + final PsiReturnStatement insertedReturnStatement = (PsiReturnStatement) body.add(returnStatement); + if (myOutputVariables.length == 1) { + final PsiExpression returnValue = insertedReturnStatement.getReturnValue(); + if (returnValue instanceof PsiReferenceExpression returnReferenceExpression) { + final PsiElement resolved = returnReferenceExpression.resolve(); + if (resolved instanceof PsiLocalVariable localVariable && Comparing.strEqual( + localVariable.getName(), + outVariableName + )) { + final PsiStatement statement = PsiTreeUtil.getPrevSiblingOfType(insertedReturnStatement, PsiStatement.class); + if (statement instanceof PsiDeclarationStatement declarationStatement) { + final PsiElement[] declaredElements = declarationStatement.getDeclaredElements(); + if (ArrayUtil.find(declaredElements, resolved) != -1) { + InlineUtil.inlineVariable(localVariable, localVariable.getInitializer(), returnReferenceExpression); + resolved.delete(); + } + } + } + } + } + } + else if (isArtificialOutputUsed()) { + body.add(myElementFactory.createStatementFromText("return " + myArtificialOutputVariable.getName() + ";", null)); + } + return exitStatementCopy; + } + + private boolean isArtificialOutputUsed() { + return myArtificialOutputVariable != null && !PsiType.VOID.equals(myReturnType) && !myIsChainedConstructor; + } + + private boolean hasNormalExit() { + boolean hasNormalExit = false; + PsiElement lastElement = myElements[myElements.length - 1]; + if (!(lastElement instanceof PsiReturnStatement || lastElement instanceof PsiBreakStatement || + lastElement instanceof PsiContinueStatement)) { + hasNormalExit = true; + } + return hasNormalExit; + } + + protected boolean isNeedToChangeCallContext() { return true; - } - } - - return applyChosenClassAndExtract(inputVariables, extractPass); - } - - @RequiredReadAction - private void declareNecessaryVariablesInsideBody(PsiCodeBlock body) throws IncorrectOperationException { - List usedVariables = - myControlFlowWrapper.getUsedVariablesInBody(ControlFlowUtil.findCodeFragment(myElements[0]), myOutputVariables); - for (PsiVariable variable : usedVariables) { - boolean toDeclare = !isDeclaredInside(variable) && myInputVariables.toDeclareInsideBody(variable); - if (toDeclare) { - String name = variable.getName(); - PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, variable.getType(), null); - body.add(statement); - } - } - - if (myArtificialOutputVariable instanceof PsiField && !myIsChainedConstructor) { - body.add(myElementFactory.createVariableDeclarationStatement( - myArtificialOutputVariable.getName(), - myArtificialOutputVariable.getType(), - null - )); - } - } - - @RequiredReadAction - protected void declareNecessaryVariablesAfterCall(PsiVariable outputVariable) throws IncorrectOperationException { - if (myHasExpressionOutput) { - return; - } - List usedVariables = myControlFlowWrapper.getUsedVariables(); - Collection reassigned = myControlFlowWrapper.getInitializedTwice(); - for (PsiVariable variable : usedVariables) { - boolean toDeclare = isDeclaredInside(variable) && !variable.equals(outputVariable); - if (toDeclare) { - String name = variable.getName(); - PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, variable.getType(), null); - if (reassigned.contains(new ControlFlowUtil.VariableInfo(variable, null))) { - final PsiElement[] psiElements = statement.getDeclaredElements(); - assert psiElements.length > 0; - PsiVariable var = (PsiVariable) psiElements[0]; - PsiUtil.setModifierProperty(var, PsiModifier.FINAL, false); - } - addToMethodCallLocation(statement); - } - } - } - - public PsiMethodCallExpression getMethodCall() { - return myMethodCall; - } - - public void setMethodCall(PsiMethodCallExpression methodCall) { - myMethodCall = methodCall; - } - - @RequiredReadAction - public boolean isDeclaredInside(PsiVariable variable) { - if (variable instanceof ImplicitVariable) { - return false; - } - int startOffset; - int endOffset; - if (myExpression != null) { - final TextRange range = myExpression.getTextRange(); - startOffset = range.getStartOffset(); - endOffset = range.getEndOffset(); - } else { - startOffset = myElements[0].getTextRange().getStartOffset(); - endOffset = myElements[myElements.length - 1].getTextRange().getEndOffset(); - } - PsiIdentifier nameIdentifier = variable.getNameIdentifier(); - if (nameIdentifier == null) { - return false; - } - final TextRange range = nameIdentifier.getTextRange(); - if (range == null) { - return false; - } - int offset = range.getStartOffset(); - return startOffset <= offset && offset <= endOffset; - } - - private String getNewVariableName(PsiVariable variable) { - for (VariableData data : myVariableDatum) { - if (data.variable.equals(variable)) { - return data.name; - } - } - return variable.getName(); - } - - private static boolean shouldAcceptCurrentTarget(Consumer extractPass, PsiElement target) { - return extractPass == null && !(target instanceof PsiAnonymousClass); - } - - private boolean applyChosenClassAndExtract(List inputVariables, @Nullable Consumer extractPass) - throws PrepareFailedException { - myStatic = shouldBeStatic(); - final Set fields = new LinkedHashSet<>(); - if (!PsiUtil.isLocalOrAnonymousClass(myTargetClass) - && (myTargetClass.getContainingClass() == null || myTargetClass.hasModifierProperty(PsiModifier.STATIC))) { - boolean canBeStatic = true; - if (myTargetClass.isInterface()) { - final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiMethod.class, false); - canBeStatic = containingMethod == null || containingMethod.hasModifierProperty(PsiModifier.STATIC); - } - if (canBeStatic) { - ElementNeedsThis needsThis = new ElementNeedsThis(myTargetClass) { - @Override - protected void visitClassMemberReferenceElement(PsiMember classMember, PsiJavaCodeReferenceElement classMemberReference) { - if (classMember instanceof PsiField && !classMember.hasModifierProperty(PsiModifier.STATIC)) { - final PsiExpression expression = PsiTreeUtil.getParentOfType(classMemberReference, PsiExpression.class, false); - if (expression == null || !PsiUtil.isAccessedForWriting(expression)) { - fields.add((PsiField) classMember); - return; - } + } + + private void declareVariableAtMethodCallLocation(String name) { + declareVariableAtMethodCallLocation(name, myReturnType); + } + + private String declareVariableAtMethodCallLocation(String name, PsiType type) { + if (myControlFlowWrapper.getOutputVariables(false).length == 0) { + PsiElement lastStatement = PsiTreeUtil.getNextSiblingOfType( + myEnclosingBlockStatement != null ? myEnclosingBlockStatement : myElements[myElements.length - 1], + PsiStatement.class + ); + if (lastStatement != null) { + name = JavaCodeStyleManager.getInstance(myProject).suggestUniqueVariableName(name, lastStatement, true); } - super.visitClassMemberReferenceElement(classMember, classMemberReference); - } - }; - for (int i = 0; i < myElements.length && !needsThis.usesMembers(); i++) { - PsiElement element = myElements[i]; - element.accept(needsThis); - } - myCanBeStatic = !needsThis.usesMembers(); - } else { - myCanBeStatic = false; - } - } else { - myCanBeStatic = false; - } - - myInputVariables = new InputVariables(inputVariables, myProject, new LocalSearchScope(myElements), isFoldingApplicable()); - myInputVariables.setUsedInstanceFields(fields); - - if (!checkExitPoints()) { - return false; - } - - checkCanBeChainedConstructor(); - - if (extractPass != null) { - extractPass.accept(this); - } - return true; - } - - protected boolean isFoldingApplicable() { - return true; - } - - private void chooseAnchor() { - myAnchor = myCodeFragmentMember; - while (!myAnchor.getParent().equals(myTargetClass)) { - myAnchor = myAnchor.getParent(); - } - } - - private void showMultipleExitPointsMessage() { - if (myShowErrorDialogs) { - HighlightManager highlightManager = HighlightManager.getInstance(myProject); - PsiStatement[] exitStatementsArray = myExitStatements.toArray(new PsiStatement[myExitStatements.size()]); - highlightManager.addOccurrenceHighlights(myEditor, exitStatementsArray, EditorColors.SEARCH_RESULT_ATTRIBUTES, true, null); - LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason( - RefactoringLocalize.thereAreMultipleExitPointsInTheSelectedCodeFragment() - ); - CommonRefactoringUtil.showErrorHint(myProject, myEditor, message.get(), myRefactoringName, myHelpId); - } - } - - @RequiredReadAction - private void showMultipleOutputMessage(PsiType expressionType) { - if (myShowErrorDialogs) { - StringBuilder buffer = new StringBuilder(); - buffer.append(RefactoringLocalize.cannotPerformRefactoringWithReason( - RefactoringLocalize.thereAreMultipleOutputValuesForTheSelectedCodeFragment() - )); - buffer.append("\n"); - if (myHasExpressionOutput) { - buffer.append(" ").append(RefactoringLocalize.expressionResult()).append(": "); - buffer.append(PsiFormatUtil.formatType(expressionType, 0, PsiSubstitutor.EMPTY)); - buffer.append(",\n"); - } - if (myGenerateConditionalExit) { - buffer.append(" ").append(RefactoringLocalize.booleanMethodResult()); - buffer.append(",\n"); - } - for (int i = 0; i < myOutputVariables.length; i++) { - PsiVariable var = myOutputVariables[i]; - buffer.append(" "); - buffer.append(var.getName()); - buffer.append(" : "); - buffer.append(PsiFormatUtil.formatType(var.getType(), 0, PsiSubstitutor.EMPTY)); - if (i < myOutputVariables.length - 1) { - buffer.append(",\n"); - } else { - buffer.append("."); - } - } - buffer.append("\nWould you like to Extract Method Object?"); - - String message = buffer.toString(); - - if (myProject.getApplication().isUnitTestMode()) { - throw new RuntimeException(message); - } - RefactoringMessageDialog dialog = new RefactoringMessageDialog( - myRefactoringName, - message, - myHelpId, - "OptionPane.errorIcon", - true, - myProject - ); - if (dialog.showAndGet()) { - new ExtractMethodObjectHandler().invoke( - myProject, - myEditor, - myTargetClass.getContainingFile(), - DataManager.getInstance().getDataContext() - ); - } + } + PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, type, myMethodCall); + statement = (PsiDeclarationStatement) addToMethodCallLocation(statement); + PsiVariable var = (PsiVariable) statement.getDeclaredElements()[0]; + myMethodCall = (PsiMethodCallExpression) var.getInitializer(); + if (myOutputVariable != null) { + var.getModifierList().replace(myOutputVariable.getModifierList()); + } + return name; + } + + private void adjustFinalParameters(final PsiMethod method) throws IncorrectOperationException { + final IncorrectOperationException[] exc = new IncorrectOperationException[1]; + exc[0] = null; + final PsiParameter[] parameters = method.getParameterList().getParameters(); + if (parameters.length > 0) { + if (CodeStyleSettingsManager.getSettings(myProject).GENERATE_FINAL_PARAMETERS) { + method.accept(new JavaRecursiveElementVisitor() { + + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + final PsiElement resolved = expression.resolve(); + if (resolved != null) { + final int index = ArrayUtil.find(parameters, resolved); + if (index >= 0) { + final PsiParameter param = parameters[index]; + if (param.hasModifierProperty(PsiModifier.FINAL) && PsiUtil.isAccessedForWriting(expression)) { + try { + PsiUtil.setModifierProperty(param, PsiModifier.FINAL, false); + } + catch (IncorrectOperationException e) { + exc[0] = e; + } + } + } + } + super.visitReferenceExpression(expression); + } + }); + } + else { + method.accept(new JavaRecursiveElementVisitor() { + @Override + public void visitReferenceExpression(PsiReferenceExpression expression) { + final PsiElement resolved = expression.resolve(); + final int index = ArrayUtil.find(parameters, resolved); + if (index >= 0) { + final PsiParameter param = parameters[index]; + if (!param.hasModifierProperty(PsiModifier.FINAL) && RefactoringUtil.isInsideAnonymousOrLocal( + expression, + method + )) { + try { + PsiUtil.setModifierProperty(param, PsiModifier.FINAL, true); + } + catch (IncorrectOperationException e) { + exc[0] = e; + } + } + } + super.visitReferenceExpression(expression); + } + }); + } + if (exc[0] != null) { + throw exc[0]; + } + } + } + + @Override + public List getDuplicates() { + if (myIsChainedConstructor) { + return filterChainedConstructorDuplicates(myDuplicates); + } + return myDuplicates; + } + + private static List filterChainedConstructorDuplicates(final List duplicates) { + List result = new ArrayList(); + for (Match duplicate : duplicates) { + final PsiElement matchStart = duplicate.getMatchStart(); + final PsiMethod method = PsiTreeUtil.getParentOfType(matchStart, PsiMethod.class); + if (method != null && method.isConstructor()) { + final PsiCodeBlock body = method.getBody(); + if (body != null) { + final PsiStatement[] psiStatements = body.getStatements(); + if (psiStatements.length > 0 && matchStart == psiStatements[0]) { + result.add(duplicate); + } + } + } + } + return result; } - } - public PsiMethod getExtractedMethod() { - return myExtractedMethod; - } + @Override + public PsiElement processMatch(Match match) throws IncorrectOperationException { + MatchUtil.changeSignature(match, myExtractedMethod); + if (RefactoringUtil.isInStaticContext(match.getMatchStart(), myExtractedMethod.getContainingClass())) { + PsiUtil.setModifierProperty(myExtractedMethod, PsiModifier.STATIC, true); + } + final PsiMethodCallExpression methodCallExpression = generateMethodCall(match.getInstanceExpression(), false); - @Override - public Boolean hasDuplicates() { - List duplicates = getDuplicates(); - if (duplicates != null && !duplicates.isEmpty()) { - return true; + ArrayList datas = new ArrayList(); + for (final VariableData variableData : myVariableDatum) { + if (variableData.passAsParameter) { + datas.add(variableData); + } + } + final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(myProject); + for (VariableData data : datas) { + final List parameterValue = match.getParameterValues(data.variable); + if (parameterValue != null) { + for (PsiElement val : parameterValue) { + if (val instanceof PsiExpression) { + final PsiType exprType = ((PsiExpression) val).getType(); + if (exprType != null && !TypeConversionUtil.isAssignable(data.type, exprType)) { + final PsiTypeCastExpression cast = (PsiTypeCastExpression) elementFactory.createExpressionFromText("(A)a", val); + cast.getCastType().replace(elementFactory.createTypeElement(data.type)); + cast.getOperand().replace(val.copy()); + val = cast; + } + } + methodCallExpression.getArgumentList().add(val); + } + } + else { + methodCallExpression.getArgumentList() + .add(myElementFactory.createExpressionFromText(data.variable.getName(), methodCallExpression)); + } + } + return match.replace(myExtractedMethod, methodCallExpression, myOutputVariable); } - if (myExtractedMethod != null) { - final ExtractMethodSignatureSuggester suggester = new ExtractMethodSignatureSuggester(myProject, myExtractedMethod, myMethodCall, myVariableDatum); - duplicates = suggester.getDuplicates(myExtractedMethod, myMethodCall, myInputVariables.getFolding()); - if (duplicates != null && !duplicates.isEmpty()) { - myDuplicates = duplicates; - myExtractedMethod = suggester.getExtractedMethod(); - myMethodCall = suggester.getMethodCall(); - myVariableDatum = suggester.getVariableData(); - return null; - } + protected void deleteExtracted() throws IncorrectOperationException { + if (myEnclosingBlockStatement == null) { + myElements[0].getParent().deleteChildRange(myElements[0], myElements[myElements.length - 1]); + } + else { + myEnclosingBlockStatement.delete(); + } + } + + protected PsiElement addToMethodCallLocation(PsiStatement statement) throws IncorrectOperationException { + if (myEnclosingBlockStatement == null) { + PsiElement containingStatement = myElements[0] instanceof PsiComment ? myElements[0] + : PsiTreeUtil.getParentOfType(myExpression != null ? myExpression : myElements[0], PsiStatement.class, false); + if (containingStatement == null) { + containingStatement = + PsiTreeUtil.getParentOfType(myExpression != null ? myExpression : myElements[0], PsiComment.class, false); + } + + return containingStatement.getParent().addBefore(statement, containingStatement); + } + else { + return myEnclosingBlockStatement.getParent().addBefore(statement, myEnclosingBlockStatement); + } + } + + private void renameInputVariables() throws IncorrectOperationException { + //when multiple input variables should have the same name, unique names are generated + //without reverse, the second rename would rename variable without a prefix into second one though it was already renamed + for (int i = myVariableDatum.length - 1; i >= 0; i--) { + VariableData data = myVariableDatum[i]; + PsiVariable variable = data.variable; + if (!data.name.equals(variable.getName())) { + for (PsiElement element : myElements) { + RefactoringUtil.renameVariableReferences(variable, data.name, new LocalSearchScope(element)); + } + } + } + } + + public PsiClass getTargetClass() { + return myTargetClass; + } + + public PsiType getReturnType() { + return myReturnType; + } + + private PsiMethod generateEmptyMethod() throws IncorrectOperationException { + return generateEmptyMethod(myMethodName, null); + } + + public PsiMethod generateEmptyMethod(String methodName, PsiElement context) throws IncorrectOperationException { + PsiMethod newMethod; + if (myIsChainedConstructor) { + newMethod = myElementFactory.createConstructor(); + } + else { + newMethod = context != null ? myElementFactory.createMethod(methodName, myReturnType, context) + : myElementFactory.createMethod(methodName, myReturnType); + PsiUtil.setModifierProperty(newMethod, PsiModifier.STATIC, isStatic()); + } + PsiUtil.setModifierProperty(newMethod, myMethodVisibility, true); + if (getTypeParameterList() != null) { + newMethod.getTypeParameterList().replace(getTypeParameterList()); + } + PsiCodeBlock body = newMethod.getBody(); + LOG.assertTrue(body != null); + + boolean isFinal = CodeStyleSettingsManager.getSettings(myProject).GENERATE_FINAL_PARAMETERS; + PsiParameterList list = newMethod.getParameterList(); + for (VariableData data : myVariableDatum) { + if (data.passAsParameter) { + PsiParameter parm = myElementFactory.createParameter(data.name, data.type); + copyParamAnnotations(parm); + if (isFinal) { + PsiUtil.setModifierProperty(parm, PsiModifier.FINAL, true); + } + list.add(parm); + } + else { + @NonNls StringBuilder buffer = new StringBuilder(); + if (isFinal) { + buffer.append("final "); + } + buffer.append("int "); + buffer.append(data.name); + buffer.append("=;"); + String text = buffer.toString(); + + PsiDeclarationStatement declaration = (PsiDeclarationStatement) myElementFactory.createStatementFromText(text, null); + declaration = (PsiDeclarationStatement) myStyleManager.reformat(declaration); + final PsiTypeElement typeElement = myElementFactory.createTypeElement(data.type); + ((PsiVariable) declaration.getDeclaredElements()[0]).getTypeElement().replace(typeElement); + body.add(declaration); + } + } + + PsiReferenceList throwsList = newMethod.getThrowsList(); + for (PsiClassType exception : getThrownExceptions()) { + throwsList.add(JavaPsiFacade.getInstance(myManager.getProject()).getElementFactory().createReferenceElementByType(exception)); + } + + if (myTargetClass.isInterface() && PsiUtil.isLanguageLevel8OrHigher(myTargetClass)) { + final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiMethod.class, false); + if (containingMethod != null && containingMethod.hasModifierProperty(PsiModifier.DEFAULT)) { + PsiUtil.setModifierProperty(newMethod, PsiModifier.DEFAULT, true); + } + } + return (PsiMethod) myStyleManager.reformat(newMethod); + } + + private void copyParamAnnotations(PsiParameter parm) { + final PsiVariable variable = PsiResolveHelper.getInstance(myProject).resolveReferencedVariable(parm.getName(), myElements[0]); + if (variable instanceof PsiParameter) { + final PsiModifierList modifierList = variable.getModifierList(); + if (modifierList != null) { + for (PsiAnnotation annotation : modifierList.getAnnotations()) { + if (SuppressWarnings.class.getName().equals(annotation.getQualifiedName())) { + continue; + } + final PsiModifierList parmModifierList = parm.getModifierList(); + LOG.assertTrue(parmModifierList != null, parm); + parmModifierList.add(annotation); + } + } + } + } + + @Nonnull + protected PsiMethodCallExpression generateMethodCall(PsiExpression instanceQualifier, final boolean generateArgs) + throws IncorrectOperationException { + @NonNls StringBuilder buffer = new StringBuilder(); + + final boolean skipInstanceQualifier; + if (myIsChainedConstructor) { + skipInstanceQualifier = true; + buffer.append(PsiKeyword.THIS); + } + else { + skipInstanceQualifier = instanceQualifier == null || instanceQualifier instanceof PsiThisExpression; + if (skipInstanceQualifier) { + if (isNeedToChangeCallContext() && myNeedChangeContext) { + boolean needsThisQualifier = false; + PsiElement parent = myCodeFragmentMember; + while (!myTargetClass.equals(parent)) { + if (parent instanceof PsiMethod) { + String methodName = ((PsiMethod) parent).getName(); + if (methodName.equals(myMethodName)) { + needsThisQualifier = true; + break; + } + } + parent = parent.getParent(); + } + if (needsThisQualifier) { + buffer.append(myTargetClass.getName()).append(".this."); + } + } + } + else { + buffer.append("qqq."); + } + + buffer.append(myMethodName); + } + buffer.append("("); + if (generateArgs) { + int count = 0; + for (VariableData data : myVariableDatum) { + if (data.passAsParameter) { + if (count > 0) { + buffer.append(","); + } + myInputVariables.appendCallArguments(data, buffer); + count++; + } + } + } + buffer.append(")"); + String text = buffer.toString(); + + PsiMethodCallExpression expr = (PsiMethodCallExpression) myElementFactory.createExpressionFromText(text, null); + expr = (PsiMethodCallExpression) myStyleManager.reformat(expr); + if (!skipInstanceQualifier) { + PsiExpression qualifierExpression = expr.getMethodExpression().getQualifierExpression(); + LOG.assertTrue(qualifierExpression != null); + qualifierExpression.replace(instanceQualifier); + } + return (PsiMethodCallExpression) JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(expr); + } + + private boolean chooseTargetClass( + PsiElement codeFragment, + final Consumer extractPass + ) throws PrepareFailedException { + final List inputVariables = myControlFlowWrapper.getInputVariables(codeFragment, myElements, myOutputVariables); + + myNeedChangeContext = false; + myTargetClass = + myCodeFragmentMember instanceof PsiMember ? ((PsiMember) myCodeFragmentMember).getContainingClass() : PsiTreeUtil.getParentOfType( + myCodeFragmentMember, + PsiClass.class + ); + if (!shouldAcceptCurrentTarget(extractPass, myTargetClass)) { + + final LinkedHashMap> classes = new LinkedHashMap<>(); + final PsiElementProcessor processor = selectedClass -> { + AnonymousTargetClassPreselectionUtil.rememberSelection(selectedClass, myTargetClass); + final List array = classes.get(selectedClass); + myNeedChangeContext = myTargetClass != selectedClass; + myTargetClass = selectedClass; + if (array != null) { + for (PsiVariable variable : array) { + if (!inputVariables.contains(variable)) { + inputVariables.addAll(array); + } + } + } + try { + return applyChosenClassAndExtract(inputVariables, extractPass); + } + catch (PrepareFailedException e) { + if (myShowErrorDialogs) { + CommonRefactoringUtil.showErrorHint( + myProject, + myEditor, + LocalizeValue.ofNullable(e.getMessage()), + ExtractMethodHandler.REFACTORING_NAME, + HelpID.EXTRACT_METHOD + ); + ExtractMethodHandler.highlightPrepareError(e, e.getFile(), myEditor, myProject); + } + return false; + } + }; + + classes.put(myTargetClass, null); + PsiElement target = myTargetClass.getParent(); + PsiElement targetMember = myTargetClass; + while (true) { + if (target instanceof PsiFile) { + break; + } + if (target instanceof PsiClass psiClass) { + boolean success = true; + final List array = new ArrayList<>(); + for (PsiElement el : myElements) { + if (!ControlFlowUtil.collectOuterLocals(array, el, myCodeFragmentMember, targetMember)) { + success = false; + break; + } + } + if (success) { + classes.put(psiClass, array); + if (shouldAcceptCurrentTarget(extractPass, target)) { + return processor.execute(psiClass); + } + } + } + targetMember = target; + target = target.getParent(); + } + + if (classes.size() > 1) { + final PsiClass[] psiClasses = classes.keySet().toArray(new PsiClass[classes.size()]); + final PsiClass preselection = AnonymousTargetClassPreselectionUtil.getPreselection(classes.keySet(), psiClasses[0]); + JBPopup popup = PopupNavigationUtil.getPsiElementPopup( + psiClasses, + new PsiClassListCellRenderer(), + "Choose Destination Class", + processor, + preselection + ); + EditorPopupHelper.getInstance().showPopupInBestPositionFor(myEditor, popup); + return true; + } + } + + return applyChosenClassAndExtract(inputVariables, extractPass); } - return false; - } - @RequiredReadAction - public boolean hasDuplicates(Set files) { - final DuplicatesFinder finder = initDuplicates(); + @RequiredReadAction + private void declareNecessaryVariablesInsideBody(PsiCodeBlock body) throws IncorrectOperationException { + List usedVariables = + myControlFlowWrapper.getUsedVariablesInBody(ControlFlowUtil.findCodeFragment(myElements[0]), myOutputVariables); + for (PsiVariable variable : usedVariables) { + boolean toDeclare = !isDeclaredInside(variable) && myInputVariables.toDeclareInsideBody(variable); + if (toDeclare) { + String name = variable.getName(); + PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, variable.getType(), null); + body.add(statement); + } + } - final Boolean hasDuplicates = hasDuplicates(); - if (hasDuplicates == null || hasDuplicates) { - return true; + if (myArtificialOutputVariable instanceof PsiField && !myIsChainedConstructor) { + body.add(myElementFactory.createVariableDeclarationStatement( + myArtificialOutputVariable.getName(), + myArtificialOutputVariable.getType(), + null + )); + } } - if (finder != null) { - final PsiManager psiManager = PsiManager.getInstance(myProject); - for (VirtualFile file : files) { - if (!finder.findDuplicates(psiManager.findFile(file)).isEmpty()) { - return true; + + @RequiredReadAction + protected void declareNecessaryVariablesAfterCall(PsiVariable outputVariable) throws IncorrectOperationException { + if (myHasExpressionOutput) { + return; + } + List usedVariables = myControlFlowWrapper.getUsedVariables(); + Collection reassigned = myControlFlowWrapper.getInitializedTwice(); + for (PsiVariable variable : usedVariables) { + boolean toDeclare = isDeclaredInside(variable) && !variable.equals(outputVariable); + if (toDeclare) { + String name = variable.getName(); + PsiDeclarationStatement statement = myElementFactory.createVariableDeclarationStatement(name, variable.getType(), null); + if (reassigned.contains(new ControlFlowUtil.VariableInfo(variable, null))) { + final PsiElement[] psiElements = statement.getDeclaredElements(); + assert psiElements.length > 0; + PsiVariable var = (PsiVariable) psiElements[0]; + PsiUtil.setModifierProperty(var, PsiModifier.FINAL, false); + } + addToMethodCallLocation(statement); + } } - } } - return false; - } - @Override - @Nullable - public String getConfirmDuplicatePrompt(Match match) { - final boolean needToBeStatic = RefactoringUtil.isInStaticContext(match.getMatchStart(), myExtractedMethod.getContainingClass()); - final String changedSignature = MatchUtil.getChangedSignature(match, myExtractedMethod, needToBeStatic, VisibilityUtil.getVisibilityStringToDisplay(myExtractedMethod)); - if (changedSignature != null) { - return RefactoringLocalize.replaceThisCodeFragmentAndChangeSignature(changedSignature).get(); + public PsiMethodCallExpression getMethodCall() { + return myMethodCall; } - if (needToBeStatic && !myExtractedMethod.hasModifierProperty(PsiModifier.STATIC)) { - return RefactoringLocalize.replaceThisCodeFragmentAndMakeMethodStatic().get(); + + public void setMethodCall(PsiMethodCallExpression methodCall) { + myMethodCall = methodCall; + } + + @RequiredReadAction + public boolean isDeclaredInside(PsiVariable variable) { + if (variable instanceof ImplicitVariable) { + return false; + } + int startOffset; + int endOffset; + if (myExpression != null) { + final TextRange range = myExpression.getTextRange(); + startOffset = range.getStartOffset(); + endOffset = range.getEndOffset(); + } + else { + startOffset = myElements[0].getTextRange().getStartOffset(); + endOffset = myElements[myElements.length - 1].getTextRange().getEndOffset(); + } + PsiIdentifier nameIdentifier = variable.getNameIdentifier(); + if (nameIdentifier == null) { + return false; + } + final TextRange range = nameIdentifier.getTextRange(); + if (range == null) { + return false; + } + int offset = range.getStartOffset(); + return startOffset <= offset && offset <= endOffset; } - return null; - } - @Override - public LocalizeValue getReplaceDuplicatesTitle(int idx, int size) { - return RefactoringLocalize.processDuplicatesTitle(idx, size); - } + private String getNewVariableName(PsiVariable variable) { + for (VariableData data : myVariableDatum) { + if (data.variable.equals(variable)) { + return data.name; + } + } + return variable.getName(); + } - public InputVariables getInputVariables() { - return myInputVariables; - } + private static boolean shouldAcceptCurrentTarget(Consumer extractPass, PsiElement target) { + return extractPass == null && !(target instanceof PsiAnonymousClass); + } - public PsiTypeParameterList getTypeParameterList() { - return myTypeParameterList; - } + private boolean applyChosenClassAndExtract(List inputVariables, @Nullable Consumer extractPass) + throws PrepareFailedException { + myStatic = shouldBeStatic(); + final Set fields = new LinkedHashSet<>(); + if (!PsiUtil.isLocalOrAnonymousClass(myTargetClass) + && (myTargetClass.getContainingClass() == null || myTargetClass.hasModifierProperty(PsiModifier.STATIC))) { + boolean canBeStatic = true; + if (myTargetClass.isInterface()) { + final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(myCodeFragmentMember, PsiMethod.class, false); + canBeStatic = containingMethod == null || containingMethod.hasModifierProperty(PsiModifier.STATIC); + } + if (canBeStatic) { + ElementNeedsThis needsThis = new ElementNeedsThis(myTargetClass) { + @Override + protected void visitClassMemberReferenceElement( + PsiMember classMember, + PsiJavaCodeReferenceElement classMemberReference + ) { + if (classMember instanceof PsiField && !classMember.hasModifierProperty(PsiModifier.STATIC)) { + final PsiExpression expression = PsiTreeUtil.getParentOfType(classMemberReference, PsiExpression.class, false); + if (expression == null || !PsiUtil.isAccessedForWriting(expression)) { + fields.add((PsiField) classMember); + return; + } + } + super.visitClassMemberReferenceElement(classMember, classMemberReference); + } + }; + for (int i = 0; i < myElements.length && !needsThis.usesMembers(); i++) { + PsiElement element = myElements[i]; + element.accept(needsThis); + } + myCanBeStatic = !needsThis.usesMembers(); + } + else { + myCanBeStatic = false; + } + } + else { + myCanBeStatic = false; + } - public PsiClassType[] getThrownExceptions() { - return myThrownExceptions; - } + myInputVariables = new InputVariables(inputVariables, myProject, new LocalSearchScope(myElements), isFoldingApplicable()); + myInputVariables.setUsedInstanceFields(fields); - public boolean isStatic() { - return myStatic; - } + if (!checkExitPoints()) { + return false; + } - public boolean isCanBeStatic() { - return myCanBeStatic; - } + checkCanBeChainedConstructor(); - public PsiElement[] getElements() { - return myElements; - } + if (extractPass != null) { + extractPass.accept(this); + } + return true; + } - public PsiVariable[] getOutputVariables() { - return myOutputVariables; - } + protected boolean isFoldingApplicable() { + return true; + } + + private void chooseAnchor() { + myAnchor = myCodeFragmentMember; + while (!myAnchor.getParent().equals(myTargetClass)) { + myAnchor = myAnchor.getParent(); + } + } + + private void showMultipleExitPointsMessage() { + if (myShowErrorDialogs) { + HighlightManager highlightManager = HighlightManager.getInstance(myProject); + PsiStatement[] exitStatementsArray = myExitStatements.toArray(new PsiStatement[myExitStatements.size()]); + highlightManager.addOccurrenceHighlights(myEditor, exitStatementsArray, EditorColors.SEARCH_RESULT_ATTRIBUTES, true, null); + LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason( + RefactoringLocalize.thereAreMultipleExitPointsInTheSelectedCodeFragment() + ); + CommonRefactoringUtil.showErrorHint(myProject, myEditor, message.get(), myRefactoringName, myHelpId); + } + } + + @RequiredReadAction + private void showMultipleOutputMessage(PsiType expressionType) { + if (myShowErrorDialogs) { + StringBuilder buffer = new StringBuilder(); + buffer.append(RefactoringLocalize.cannotPerformRefactoringWithReason( + RefactoringLocalize.thereAreMultipleOutputValuesForTheSelectedCodeFragment() + )); + buffer.append("\n"); + if (myHasExpressionOutput) { + buffer.append(" ").append(RefactoringLocalize.expressionResult()).append(": "); + buffer.append(PsiFormatUtil.formatType(expressionType, 0, PsiSubstitutor.EMPTY)); + buffer.append(",\n"); + } + if (myGenerateConditionalExit) { + buffer.append(" ").append(RefactoringLocalize.booleanMethodResult()); + buffer.append(",\n"); + } + for (int i = 0; i < myOutputVariables.length; i++) { + PsiVariable var = myOutputVariables[i]; + buffer.append(" "); + buffer.append(var.getName()); + buffer.append(" : "); + buffer.append(PsiFormatUtil.formatType(var.getType(), 0, PsiSubstitutor.EMPTY)); + if (i < myOutputVariables.length - 1) { + buffer.append(",\n"); + } + else { + buffer.append("."); + } + } + buffer.append("\nWould you like to Extract Method Object?"); + + String message = buffer.toString(); + + if (myProject.getApplication().isUnitTestMode()) { + throw new RuntimeException(message); + } + RefactoringMessageDialog dialog = new RefactoringMessageDialog( + myRefactoringName, + message, + myHelpId, + "OptionPane.errorIcon", + true, + myProject + ); + if (dialog.showAndGet()) { + new ExtractMethodObjectHandler().invoke( + myProject, + myEditor, + myTargetClass.getContainingFile(), + DataManager.getInstance().getDataContext() + ); + } + } + } + + public PsiMethod getExtractedMethod() { + return myExtractedMethod; + } + + @Override + public Boolean hasDuplicates() { + List duplicates = getDuplicates(); + if (duplicates != null && !duplicates.isEmpty()) { + return true; + } + + if (myExtractedMethod != null) { + final ExtractMethodSignatureSuggester suggester = + new ExtractMethodSignatureSuggester(myProject, myExtractedMethod, myMethodCall, myVariableDatum); + duplicates = suggester.getDuplicates(myExtractedMethod, myMethodCall, myInputVariables.getFolding()); + if (duplicates != null && !duplicates.isEmpty()) { + myDuplicates = duplicates; + myExtractedMethod = suggester.getExtractedMethod(); + myMethodCall = suggester.getMethodCall(); + myVariableDatum = suggester.getVariableData(); + return null; + } + } + return false; + } + + @RequiredReadAction + public boolean hasDuplicates(Set files) { + final DuplicatesFinder finder = initDuplicates(); + + final Boolean hasDuplicates = hasDuplicates(); + if (hasDuplicates == null || hasDuplicates) { + return true; + } + if (finder != null) { + final PsiManager psiManager = PsiManager.getInstance(myProject); + for (VirtualFile file : files) { + if (!finder.findDuplicates(psiManager.findFile(file)).isEmpty()) { + return true; + } + } + } + return false; + } + + @Override + @Nullable + public String getConfirmDuplicatePrompt(Match match) { + final boolean needToBeStatic = RefactoringUtil.isInStaticContext(match.getMatchStart(), myExtractedMethod.getContainingClass()); + final String changedSignature = MatchUtil.getChangedSignature( + match, + myExtractedMethod, + needToBeStatic, + VisibilityUtil.getVisibilityStringToDisplay(myExtractedMethod) + ); + if (changedSignature != null) { + return RefactoringLocalize.replaceThisCodeFragmentAndChangeSignature(changedSignature).get(); + } + if (needToBeStatic && !myExtractedMethod.hasModifierProperty(PsiModifier.STATIC)) { + return RefactoringLocalize.replaceThisCodeFragmentAndMakeMethodStatic().get(); + } + return null; + } + + @Override + public LocalizeValue getReplaceDuplicatesTitle(int idx, int size) { + return RefactoringLocalize.processDuplicatesTitle(idx, size); + } + + public InputVariables getInputVariables() { + return myInputVariables; + } + + public PsiTypeParameterList getTypeParameterList() { + return myTypeParameterList; + } + + public PsiClassType[] getThrownExceptions() { + return myThrownExceptions; + } + + public boolean isStatic() { + return myStatic; + } + + public boolean isCanBeStatic() { + return myCanBeStatic; + } + + public PsiElement[] getElements() { + return myElements; + } + + public PsiVariable[] getOutputVariables() { + return myOutputVariables; + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethodObject/ExtractMethodObjectDialog.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethodObject/ExtractMethodObjectDialog.java index 11a203ab5..886aca9b7 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethodObject/ExtractMethodObjectDialog.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractMethodObject/ExtractMethodObjectDialog.java @@ -30,6 +30,7 @@ import consulo.language.editor.refactoring.ui.ConflictsDialog; import consulo.language.editor.ui.awt.EditorTextField; import consulo.language.psi.PsiElement; +import consulo.localize.LocalizeValue; import consulo.project.Project; import consulo.ui.ex.awt.DialogWrapper; import consulo.ui.ex.awt.UIUtil; @@ -38,6 +39,7 @@ import org.jetbrains.annotations.NonNls; import jakarta.annotation.Nonnull; + import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; @@ -45,400 +47,412 @@ import java.util.Enumeration; import java.util.function.Function; - public class ExtractMethodObjectDialog extends DialogWrapper implements AbstractExtractDialog { - private final Project myProject; - private final PsiType myReturnType; - private final PsiTypeParameterList myTypeParameterList; - private final PsiType[] myExceptions; - private final boolean myStaticFlag; - private final boolean myCanBeStatic; - private final PsiElement[] myElementsToExtract; - private final boolean myMultipleExitPoints; - - private final InputVariables myVariableData; - private final PsiClass myTargetClass; - private final boolean myWasStatic; - - - private JRadioButton myCreateInnerClassRb; - private JRadioButton myCreateAnonymousClassWrapperRb; - private JTextArea mySignatureArea; - private JCheckBox myCbMakeStatic; - private JCheckBox myCbMakeVarargs; - private JCheckBox myCbMakeVarargsAnonymous; - - private JPanel myWholePanel; - private JPanel myParametersTableContainer; - private JRadioButton myPrivateRadioButton; - private JRadioButton myProtectedRadioButton; - private JRadioButton myPackageLocalRadioButton; - private JRadioButton myPublicRadioButton; - - private EditorTextField myInnerClassName; - private EditorTextField myMethodName; - - private JPanel myInnerClassPanel; - private JPanel myAnonymousClassPanel; - private JCheckBox myFoldCb; - private ButtonGroup myVisibilityGroup; - private VariableData[] myInputVariables; - - - public ExtractMethodObjectDialog(Project project, - PsiClass targetClass, - final InputVariables inputVariables, - PsiType returnType, - PsiTypeParameterList typeParameterList, - PsiType[] exceptions, - boolean isStatic, - boolean canBeStatic, - final PsiElement[] elementsToExtract, - final boolean multipleExitPoints) { - super(project, true); - myProject = project; - myTargetClass = targetClass; - myReturnType = returnType; - myTypeParameterList = typeParameterList; - myExceptions = exceptions; - myStaticFlag = isStatic; - myCanBeStatic = canBeStatic; - myElementsToExtract = elementsToExtract; - myMultipleExitPoints = multipleExitPoints; - - boolean canBeVarargs = false; - for (VariableData data : inputVariables.getInputVariables()) { - canBeVarargs |= data.type instanceof PsiArrayType; - } - canBeVarargs |= inputVariables.isFoldable() && inputVariables.isFoldingSelectedByDefault(); - myWasStatic = canBeVarargs; + private final Project myProject; + private final PsiType myReturnType; + private final PsiTypeParameterList myTypeParameterList; + private final PsiType[] myExceptions; + private final boolean myStaticFlag; + private final boolean myCanBeStatic; + private final PsiElement[] myElementsToExtract; + private final boolean myMultipleExitPoints; + + private final InputVariables myVariableData; + private final PsiClass myTargetClass; + private final boolean myWasStatic; + + + private JRadioButton myCreateInnerClassRb; + private JRadioButton myCreateAnonymousClassWrapperRb; + private JTextArea mySignatureArea; + private JCheckBox myCbMakeStatic; + private JCheckBox myCbMakeVarargs; + private JCheckBox myCbMakeVarargsAnonymous; + + private JPanel myWholePanel; + private JPanel myParametersTableContainer; + private JRadioButton myPrivateRadioButton; + private JRadioButton myProtectedRadioButton; + private JRadioButton myPackageLocalRadioButton; + private JRadioButton myPublicRadioButton; + + private EditorTextField myInnerClassName; + private EditorTextField myMethodName; + + private JPanel myInnerClassPanel; + private JPanel myAnonymousClassPanel; + private JCheckBox myFoldCb; + private ButtonGroup myVisibilityGroup; + private VariableData[] myInputVariables; + + + public ExtractMethodObjectDialog( + Project project, + PsiClass targetClass, + final InputVariables inputVariables, + PsiType returnType, + PsiTypeParameterList typeParameterList, + PsiType[] exceptions, + boolean isStatic, + boolean canBeStatic, + final PsiElement[] elementsToExtract, + final boolean multipleExitPoints + ) { + super(project, true); + myProject = project; + myTargetClass = targetClass; + myReturnType = returnType; + myTypeParameterList = typeParameterList; + myExceptions = exceptions; + myStaticFlag = isStatic; + myCanBeStatic = canBeStatic; + myElementsToExtract = elementsToExtract; + myMultipleExitPoints = multipleExitPoints; + + boolean canBeVarargs = false; + for (VariableData data : inputVariables.getInputVariables()) { + canBeVarargs |= data.type instanceof PsiArrayType; + } + canBeVarargs |= inputVariables.isFoldable() && inputVariables.isFoldingSelectedByDefault(); + myWasStatic = canBeVarargs; - myVariableData = inputVariables; + myVariableData = inputVariables; - setTitle(ExtractMethodObjectProcessor.REFACTORING_NAME); + setTitle(ExtractMethodObjectProcessor.REFACTORING_NAME); - // Create UI components + // Create UI components - myCbMakeVarargs.setVisible(canBeVarargs); - myCbMakeVarargsAnonymous.setVisible(canBeVarargs); + myCbMakeVarargs.setVisible(canBeVarargs); + myCbMakeVarargsAnonymous.setVisible(canBeVarargs); - // Initialize UI - init(); + // Initialize UI + init(); - } + } - public boolean isMakeStatic() { - if (myStaticFlag) { - return true; + public boolean isMakeStatic() { + if (myStaticFlag) { + return true; + } + if (!myCanBeStatic) { + return false; + } + return myCbMakeStatic.isSelected(); } - if (!myCanBeStatic) { - return false; + + public boolean isChainedConstructor() { + return false; } - return myCbMakeStatic.isSelected(); - } - - public boolean isChainedConstructor() { - return false; - } - - @Override - public PsiType getReturnType() { - return null; - } - - @Nonnull - protected Action[] createActions() { - return new Action[]{ - getOKAction(), - getCancelAction(), - getHelpAction() - }; - } - - public String getChosenMethodName() { - return myCreateInnerClassRb.isSelected() ? myInnerClassName.getText() : myMethodName.getText(); - } - - public VariableData[] getChosenParameters() { - return myInputVariables; - } - - public JComponent getPreferredFocusedComponent() { - return myInnerClassName; - } - - protected void doHelpAction() { - HelpManager.getInstance().invokeHelp(HelpID.EXTRACT_METHOD_OBJECT); - } - - protected void doOKAction() { - MultiMap conflicts = new MultiMap(); - if (myCreateInnerClassRb.isSelected()) { - final PsiClass innerClass = myTargetClass.findInnerClassByName(myInnerClassName.getText(), false); - if (innerClass != null) { - conflicts.putValue(innerClass, "Inner class " + myInnerClassName.getText() + " already defined in class " + myTargetClass.getName()); - } + + @Override + public PsiType getReturnType() { + return null; } - if (conflicts.size() > 0) { - final ConflictsDialog conflictsDialog = new ConflictsDialog(myProject, conflicts); - if (!conflictsDialog.showAndGet()) { - if (conflictsDialog.isShowConflicts()) { - close(CANCEL_EXIT_CODE); - } - return; - } + + @Nonnull + protected Action[] createActions() { + return new Action[]{ + getOKAction(), + getCancelAction(), + getHelpAction() + }; } - final JCheckBox makeVarargsCb = myCreateInnerClassRb.isSelected() ? myCbMakeVarargs : myCbMakeVarargsAnonymous; - if (makeVarargsCb != null && makeVarargsCb.isSelected()) { - final VariableData data = myInputVariables[myInputVariables.length - 1]; - if (data.type instanceof PsiArrayType) { - data.type = new PsiEllipsisType(((PsiArrayType) data.type).getComponentType()); - } + public String getChosenMethodName() { + return myCreateInnerClassRb.isSelected() ? myInnerClassName.getText() : myMethodName.getText(); } - super.doOKAction(); - } - - private void updateVarargsEnabled() { - final boolean enabled = myInputVariables.length > 0 && myInputVariables[myInputVariables.length - 1].type instanceof PsiArrayType; - if (myCreateInnerClassRb.isSelected()) { - myCbMakeVarargs.setEnabled(enabled); - } else { - myCbMakeVarargsAnonymous.setEnabled(enabled); + + public VariableData[] getChosenParameters() { + return myInputVariables; } - } - - private void update() { - myCbMakeStatic.setEnabled(myCreateInnerClassRb.isSelected() && myCanBeStatic && !myStaticFlag); - updateSignature(); - final PsiNameHelper helper = PsiNameHelper.getInstance(myProject); - setOKActionEnabled((myCreateInnerClassRb.isSelected() && helper.isIdentifier(myInnerClassName.getText())) || (!myCreateInnerClassRb.isSelected() && helper.isIdentifier(myMethodName.getText() - ))); - } - - public String getVisibility() { - if (myPublicRadioButton.isSelected()) { - return PsiModifier.PUBLIC; + + public JComponent getPreferredFocusedComponent() { + return myInnerClassName; } - if (myPackageLocalRadioButton.isSelected()) { - return PsiModifier.PACKAGE_LOCAL; + + protected void doHelpAction() { + HelpManager.getInstance().invokeHelp(HelpID.EXTRACT_METHOD_OBJECT); } - if (myProtectedRadioButton.isSelected()) { - return PsiModifier.PROTECTED; + + protected void doOKAction() { + MultiMap conflicts = new MultiMap<>(); + if (myCreateInnerClassRb.isSelected()) { + final PsiClass innerClass = myTargetClass.findInnerClassByName(myInnerClassName.getText(), false); + if (innerClass != null) { + conflicts.putValue( + innerClass, + LocalizeValue.localizeTODO( + "Inner class " + myInnerClassName.getText() + " already defined in class " + myTargetClass.getName() + ) + ); + } + } + if (conflicts.size() > 0) { + final ConflictsDialog conflictsDialog = new ConflictsDialog(myProject, conflicts); + if (!conflictsDialog.showAndGet()) { + if (conflictsDialog.isShowConflicts()) { + close(CANCEL_EXIT_CODE); + } + return; + } + } + + final JCheckBox makeVarargsCb = myCreateInnerClassRb.isSelected() ? myCbMakeVarargs : myCbMakeVarargsAnonymous; + if (makeVarargsCb != null && makeVarargsCb.isSelected()) { + final VariableData data = myInputVariables[myInputVariables.length - 1]; + if (data.type instanceof PsiArrayType) { + data.type = new PsiEllipsisType(((PsiArrayType) data.type).getComponentType()); + } + } + super.doOKAction(); + } + + private void updateVarargsEnabled() { + final boolean enabled = myInputVariables.length > 0 && myInputVariables[myInputVariables.length - 1].type instanceof PsiArrayType; + if (myCreateInnerClassRb.isSelected()) { + myCbMakeVarargs.setEnabled(enabled); + } + else { + myCbMakeVarargsAnonymous.setEnabled(enabled); + } + } + + private void update() { + myCbMakeStatic.setEnabled(myCreateInnerClassRb.isSelected() && myCanBeStatic && !myStaticFlag); + updateSignature(); + final PsiNameHelper helper = PsiNameHelper.getInstance(myProject); + setOKActionEnabled((myCreateInnerClassRb.isSelected() && helper.isIdentifier(myInnerClassName.getText())) || (!myCreateInnerClassRb.isSelected() && helper.isIdentifier( + myMethodName.getText() + ))); } - if (myPrivateRadioButton.isSelected()) { - return PsiModifier.PRIVATE; + + public String getVisibility() { + if (myPublicRadioButton.isSelected()) { + return PsiModifier.PUBLIC; + } + if (myPackageLocalRadioButton.isSelected()) { + return PsiModifier.PACKAGE_LOCAL; + } + if (myProtectedRadioButton.isSelected()) { + return PsiModifier.PROTECTED; + } + if (myPrivateRadioButton.isSelected()) { + return PsiModifier.PRIVATE; + } + return null; } - return null; - } - - - protected JComponent createCenterPanel() { - mySignatureArea.setEditable(false); - myCreateInnerClassRb.setSelected(true); - - final ActionListener enableDisableListener = new ActionListener() { - public void actionPerformed(final ActionEvent e) { - enable(myCreateInnerClassRb.isSelected()); - } - }; - myCreateInnerClassRb.addActionListener(enableDisableListener); - myCreateAnonymousClassWrapperRb.addActionListener(enableDisableListener); - myCreateAnonymousClassWrapperRb.setEnabled(!myMultipleExitPoints); - - myFoldCb.setSelected(myVariableData.isFoldingSelectedByDefault()); - myFoldCb.setVisible(myVariableData.isFoldable()); - myVariableData.setFoldingAvailable(myFoldCb.isSelected()); - myInputVariables = myVariableData.getInputVariables().toArray(new VariableData[myVariableData.getInputVariables().size()]); - myFoldCb.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { + + + protected JComponent createCenterPanel() { + mySignatureArea.setEditable(false); + myCreateInnerClassRb.setSelected(true); + + final ActionListener enableDisableListener = new ActionListener() { + public void actionPerformed(final ActionEvent e) { + enable(myCreateInnerClassRb.isSelected()); + } + }; + myCreateInnerClassRb.addActionListener(enableDisableListener); + myCreateAnonymousClassWrapperRb.addActionListener(enableDisableListener); + myCreateAnonymousClassWrapperRb.setEnabled(!myMultipleExitPoints); + + myFoldCb.setSelected(myVariableData.isFoldingSelectedByDefault()); + myFoldCb.setVisible(myVariableData.isFoldable()); myVariableData.setFoldingAvailable(myFoldCb.isSelected()); myInputVariables = myVariableData.getInputVariables().toArray(new VariableData[myVariableData.getInputVariables().size()]); - myParametersTableContainer.removeAll(); + myFoldCb.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + myVariableData.setFoldingAvailable(myFoldCb.isSelected()); + myInputVariables = myVariableData.getInputVariables().toArray(new VariableData[myVariableData.getInputVariables().size()]); + myParametersTableContainer.removeAll(); + myParametersTableContainer.add(createParametersPanel(), BorderLayout.CENTER); + myParametersTableContainer.revalidate(); + updateSignature(); + updateVarargsEnabled(); + } + }); myParametersTableContainer.add(createParametersPanel(), BorderLayout.CENTER); - myParametersTableContainer.revalidate(); - updateSignature(); - updateVarargsEnabled(); - } - }); - myParametersTableContainer.add(createParametersPanel(), BorderLayout.CENTER); - final ActionListener updateSugnatureListener = new ActionListener() { - public void actionPerformed(final ActionEvent e) { - updateSignature(); - ApplicationIdeFocusManager.getInstance().getInstanceForProject(myProject).requestFocus(myCreateInnerClassRb.isSelected() ? myInnerClassName : myMethodName, false); - } - }; - - if (myStaticFlag || myCanBeStatic) { - myCbMakeStatic.setEnabled(!myStaticFlag); - myCbMakeStatic.setSelected(myStaticFlag); - - myCbMakeStatic.addActionListener(updateSugnatureListener); - } else { - myCbMakeStatic.setSelected(false); - myCbMakeStatic.setEnabled(false); - } + final ActionListener updateSugnatureListener = new ActionListener() { + public void actionPerformed(final ActionEvent e) { + updateSignature(); + ApplicationIdeFocusManager.getInstance() + .getInstanceForProject(myProject) + .requestFocus(myCreateInnerClassRb.isSelected() ? myInnerClassName : myMethodName, false); + } + }; - updateVarargsEnabled(); + if (myStaticFlag || myCanBeStatic) { + myCbMakeStatic.setEnabled(!myStaticFlag); + myCbMakeStatic.setSelected(myStaticFlag); - myCbMakeVarargs.setSelected(myWasStatic); - myCbMakeVarargs.addActionListener(updateSugnatureListener); + myCbMakeStatic.addActionListener(updateSugnatureListener); + } + else { + myCbMakeStatic.setSelected(false); + myCbMakeStatic.setEnabled(false); + } - myCbMakeVarargsAnonymous.setSelected(myWasStatic); - myCbMakeVarargsAnonymous.addActionListener(updateSugnatureListener); + updateVarargsEnabled(); - final DocumentAdapter nameListener = new DocumentAdapter() { - @Override - public void documentChanged(final DocumentEvent e) { - update(); - } - }; - myInnerClassName.getDocument().addDocumentListener(nameListener); - myMethodName.getDocument().addDocumentListener(nameListener); + myCbMakeVarargs.setSelected(myWasStatic); + myCbMakeVarargs.addActionListener(updateSugnatureListener); + + myCbMakeVarargsAnonymous.setSelected(myWasStatic); + myCbMakeVarargsAnonymous.addActionListener(updateSugnatureListener); - myPrivateRadioButton.setSelected(true); + final DocumentAdapter nameListener = new DocumentAdapter() { + @Override + public void documentChanged(final DocumentEvent e) { + update(); + } + }; + myInnerClassName.getDocument().addDocumentListener(nameListener); + myMethodName.getDocument().addDocumentListener(nameListener); - myCreateInnerClassRb.addActionListener(updateSugnatureListener); - myCreateAnonymousClassWrapperRb.addActionListener(updateSugnatureListener); + myPrivateRadioButton.setSelected(true); - final Enumeration visibilities = myVisibilityGroup.getElements(); - while (visibilities.hasMoreElements()) { - visibilities.nextElement().addActionListener(updateSugnatureListener); + myCreateInnerClassRb.addActionListener(updateSugnatureListener); + myCreateAnonymousClassWrapperRb.addActionListener(updateSugnatureListener); + + final Enumeration visibilities = myVisibilityGroup.getElements(); + while (visibilities.hasMoreElements()) { + visibilities.nextElement().addActionListener(updateSugnatureListener); + } + + enable(true); + return myWholePanel; } - enable(true); - return myWholePanel; - } + private void enable(boolean innerClassSelected) { + UIUtil.setEnabled(myInnerClassPanel, innerClassSelected, true); + UIUtil.setEnabled(myAnonymousClassPanel, !innerClassSelected, true); + update(); + } - private void enable(boolean innerClassSelected) { - UIUtil.setEnabled(myInnerClassPanel, innerClassSelected, true); - UIUtil.setEnabled(myAnonymousClassPanel, !innerClassSelected, true); - update(); - } + private JComponent createParametersPanel() { + return new ParameterTablePanel(myProject, myInputVariables, myElementsToExtract) { + protected void updateSignature() { + updateVarargsEnabled(); + ExtractMethodObjectDialog.this.updateSignature(); + } + + protected void doEnterAction() { + clickDefaultButton(); + } + + protected void doCancelAction() { + ExtractMethodObjectDialog.this.doCancelAction(); + } + + @Override + protected boolean isUsedAfter(PsiVariable variable) { + return ExtractMethodObjectDialog.this.isUsedAfter(variable); + } + }; + } - private JComponent createParametersPanel() { - return new ParameterTablePanel(myProject, myInputVariables, myElementsToExtract) { - protected void updateSignature() { - updateVarargsEnabled(); - ExtractMethodObjectDialog.this.updateSignature(); - } - - protected void doEnterAction() { - clickDefaultButton(); - } - - protected void doCancelAction() { - ExtractMethodObjectDialog.this.doCancelAction(); - } - - @Override - protected boolean isUsedAfter(PsiVariable variable) { - return ExtractMethodObjectDialog.this.isUsedAfter(variable); - } - }; - } - - protected boolean isUsedAfter(PsiVariable variable) { - return false; - } - - protected void updateSignature() { - if (mySignatureArea == null) { - return; + protected boolean isUsedAfter(PsiVariable variable) { + return false; } - @NonNls StringBuffer buffer = getSignature(); - mySignatureArea.setText(buffer.toString()); - } - - protected StringBuffer getSignature() { - final String INDENT = " "; - @NonNls StringBuffer buffer = new StringBuffer(); - final String visibilityString = VisibilityUtil.getVisibilityString(getVisibility()); - if (myCreateInnerClassRb.isSelected()) { - buffer.append(visibilityString); - if (buffer.length() > 0) { - buffer.append(" "); - } - if (isMakeStatic()) { - buffer.append("static "); - } - buffer.append("class "); - buffer.append(myInnerClassName.getText()); - if (myTypeParameterList != null) { - buffer.append(myTypeParameterList.getText()); - buffer.append(" "); - } - buffer.append("{\n"); - buffer.append(INDENT); - buffer.append("public "); - buffer.append(myInnerClassName.getText()); - methodSignature(INDENT, buffer); - buffer.append("\n}"); - } else { - buffer.append("new Object(){\n"); - buffer.append(INDENT); - buffer.append("private "); - buffer.append(PsiFormatUtil.formatType(myReturnType, 0, PsiSubstitutor.EMPTY)); - buffer.append(" "); - buffer.append(myMethodName.getText()); - methodSignature(INDENT, buffer); - buffer.append("\n}."); - buffer.append(myMethodName.getText()); - buffer.append("("); - buffer.append(StringUtil.join(myInputVariables, new Function() { - public String apply(final VariableData variableData) { - return variableData.name; + + protected void updateSignature() { + if (mySignatureArea == null) { + return; } - }, ", ")); - buffer.append(")"); + @NonNls StringBuffer buffer = getSignature(); + mySignatureArea.setText(buffer.toString()); } - return buffer; - } - - private void methodSignature(final String INDENT, final StringBuffer buffer) { - buffer.append("("); - int count = 0; - final String indent = " "; - for (int i = 0; i < myInputVariables.length; i++) { - VariableData data = myInputVariables[i]; - if (data.passAsParameter) { - //String typeAndModifiers = PsiFormatUtil.formatVariable(data.variable, - // PsiFormatUtil.SHOW_MODIFIERS | PsiFormatUtil.SHOW_TYPE); - PsiType type = data.type; - if (i == myInputVariables.length - 1 && type instanceof PsiArrayType && ((myCreateInnerClassRb.isSelected() && myCbMakeVarargs.isSelected()) || (myCreateAnonymousClassWrapperRb - .isSelected() && myCbMakeVarargsAnonymous.isSelected()))) { - type = new PsiEllipsisType(((PsiArrayType) type).getComponentType()); + protected StringBuffer getSignature() { + final String INDENT = " "; + @NonNls StringBuffer buffer = new StringBuffer(); + final String visibilityString = VisibilityUtil.getVisibilityString(getVisibility()); + if (myCreateInnerClassRb.isSelected()) { + buffer.append(visibilityString); + if (buffer.length() > 0) { + buffer.append(" "); + } + if (isMakeStatic()) { + buffer.append("static "); + } + buffer.append("class "); + buffer.append(myInnerClassName.getText()); + if (myTypeParameterList != null) { + buffer.append(myTypeParameterList.getText()); + buffer.append(" "); + } + buffer.append("{\n"); + buffer.append(INDENT); + buffer.append("public "); + buffer.append(myInnerClassName.getText()); + methodSignature(INDENT, buffer); + buffer.append("\n}"); } - - String typeText = type.getPresentableText(); - if (count > 0) { - buffer.append(", "); + else { + buffer.append("new Object(){\n"); + buffer.append(INDENT); + buffer.append("private "); + buffer.append(PsiFormatUtil.formatType(myReturnType, 0, PsiSubstitutor.EMPTY)); + buffer.append(" "); + buffer.append(myMethodName.getText()); + methodSignature(INDENT, buffer); + buffer.append("\n}."); + buffer.append(myMethodName.getText()); + buffer.append("("); + buffer.append(StringUtil.join(myInputVariables, new Function() { + public String apply(final VariableData variableData) { + return variableData.name; + } + }, ", ")); + buffer.append(")"); } - buffer.append("\n"); - buffer.append(indent); - buffer.append(typeText); - buffer.append(" "); - buffer.append(data.name); - count++; - } + + return buffer; } - buffer.append(")"); - if (myExceptions.length > 0) { - buffer.append("\n"); - buffer.append("throws\n"); - for (PsiType exception : myExceptions) { - buffer.append(INDENT); - buffer.append(PsiFormatUtil.formatType(exception, 0, PsiSubstitutor.EMPTY)); - buffer.append("\n"); - } + + private void methodSignature(final String INDENT, final StringBuffer buffer) { + buffer.append("("); + int count = 0; + final String indent = " "; + for (int i = 0; i < myInputVariables.length; i++) { + VariableData data = myInputVariables[i]; + if (data.passAsParameter) { + //String typeAndModifiers = PsiFormatUtil.formatVariable(data.variable, + // PsiFormatUtil.SHOW_MODIFIERS | PsiFormatUtil.SHOW_TYPE); + PsiType type = data.type; + if (i == myInputVariables.length - 1 && type instanceof PsiArrayType && ((myCreateInnerClassRb.isSelected() && myCbMakeVarargs.isSelected()) || (myCreateAnonymousClassWrapperRb + .isSelected() && myCbMakeVarargsAnonymous.isSelected()))) { + type = new PsiEllipsisType(((PsiArrayType) type).getComponentType()); + } + + String typeText = type.getPresentableText(); + if (count > 0) { + buffer.append(", "); + } + buffer.append("\n"); + buffer.append(indent); + buffer.append(typeText); + buffer.append(" "); + buffer.append(data.name); + count++; + } + } + buffer.append(")"); + if (myExceptions.length > 0) { + buffer.append("\n"); + buffer.append("throws\n"); + for (PsiType exception : myExceptions) { + buffer.append(INDENT); + buffer.append(PsiFormatUtil.formatType(exception, 0, PsiSubstitutor.EMPTY)); + buffer.append("\n"); + } + } + buffer.append("{}"); } - buffer.append("{}"); - } - public boolean createInnerClass() { - return myCreateInnerClassRb.isSelected(); - } + public boolean createInnerClass() { + return myCreateInnerClassRb.isSelected(); + } } diff --git a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractSuperclass/ExtractSuperclassHandler.java b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractSuperclass/ExtractSuperclassHandler.java index e602a6229..f4b0230f9 100644 --- a/plugin/src/main/java/com/intellij/java/impl/refactoring/extractSuperclass/ExtractSuperclassHandler.java +++ b/plugin/src/main/java/com/intellij/java/impl/refactoring/extractSuperclass/ExtractSuperclassHandler.java @@ -13,11 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -/** - * created at Oct 25, 2001 - * @author Jeka - */ package com.intellij.java.impl.refactoring.extractSuperclass; import com.intellij.java.impl.refactoring.HelpID; @@ -56,144 +51,164 @@ import javax.swing.*; import java.util.List; +/** + * @since 2001-10-25 + * @author Jeka + */ public class ExtractSuperclassHandler implements RefactoringActionHandler, ExtractSuperclassDialog.Callback, ElementsHandler { - private static final Logger LOG = Logger.getInstance(ExtractSuperclassHandler.class); - - public static final LocalizeValue REFACTORING_NAME = RefactoringLocalize.extractSuperclassTitle(); - - private PsiClass mySubclass; - private Project myProject; - - @RequiredUIAccess - public void invoke(@Nonnull Project project, Editor editor, PsiFile file, DataContext dataContext) { - editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); - int offset = editor.getCaretModel().getOffset(); - PsiElement element = file.findElementAt(offset); - while (true) { - if (element == null || element instanceof PsiFile) { - LocalizeValue message = RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.errorWrongCaretPositionClass()); - CommonRefactoringUtil.showErrorHint(project, editor, message.get(), REFACTORING_NAME.get(), HelpID.EXTRACT_SUPERCLASS); - return; - } - if (element instanceof PsiClass && !(element instanceof PsiAnonymousClass)) { - invoke(project, new PsiElement[]{element}, dataContext); - return; - } - element = element.getParent(); + private static final Logger LOG = Logger.getInstance(ExtractSuperclassHandler.class); + + public static final LocalizeValue REFACTORING_NAME = RefactoringLocalize.extractSuperclassTitle(); + + private PsiClass mySubclass; + private Project myProject; + + @RequiredUIAccess + public void invoke(@Nonnull Project project, Editor editor, PsiFile file, DataContext dataContext) { + editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); + int offset = editor.getCaretModel().getOffset(); + PsiElement element = file.findElementAt(offset); + while (true) { + if (element == null || element instanceof PsiFile) { + LocalizeValue message = + RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.errorWrongCaretPositionClass()); + CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.EXTRACT_SUPERCLASS); + return; + } + if (element instanceof PsiClass && !(element instanceof PsiAnonymousClass)) { + invoke(project, new PsiElement[]{element}, dataContext); + return; + } + element = element.getParent(); + } } - } - - public void invoke(@Nonnull final Project project, @Nonnull PsiElement[] elements, DataContext dataContext) { - if (elements.length != 1) return; - myProject = project; - mySubclass = (PsiClass)elements[0]; - - if (!CommonRefactoringUtil.checkReadOnlyStatus(project, mySubclass)) return; - - Editor editor = dataContext != null ? dataContext.getData(Editor.KEY) : null; - if (mySubclass.isInterface()) { - LocalizeValue message = - RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.superclassCannotBeExtractedFromAnInterface()); - CommonRefactoringUtil.showErrorHint(project, editor, message.get(), REFACTORING_NAME.get(), HelpID.EXTRACT_SUPERCLASS); - return; + @Override + @RequiredUIAccess + public void invoke(@Nonnull Project project, @Nonnull PsiElement[] elements, DataContext dataContext) { + if (elements.length != 1) { + return; + } + + myProject = project; + mySubclass = (PsiClass) elements[0]; + + if (!CommonRefactoringUtil.checkReadOnlyStatus(project, mySubclass)) { + return; + } + + Editor editor = dataContext != null ? dataContext.getData(Editor.KEY) : null; + if (mySubclass.isInterface()) { + LocalizeValue message = + RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.superclassCannotBeExtractedFromAnInterface()); + CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.EXTRACT_SUPERCLASS); + return; + } + + if (mySubclass.isEnum()) { + LocalizeValue message = + RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.superclassCannotBeExtractedFromAnEnum()); + CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HelpID.EXTRACT_SUPERCLASS); + return; + } + + List memberInfos = MemberInfo.extractClassMembers(mySubclass, element -> true, false); + + ExtractSuperclassDialog dialog = + new ExtractSuperclassDialog(project, mySubclass, memberInfos, ExtractSuperclassHandler.this); + dialog.show(); + if (!dialog.isOK() || !dialog.isExtractSuperclass()) { + return; + } + + CommandProcessor.getInstance().newCommand() + .project(myProject) + .name(REFACTORING_NAME) + .inWriteAction() + .run(() -> doRefactoring(project, mySubclass, dialog)); } - if (mySubclass.isEnum()) { - LocalizeValue message = - RefactoringLocalize.cannotPerformRefactoringWithReason(RefactoringLocalize.superclassCannotBeExtractedFromAnEnum()); - CommonRefactoringUtil.showErrorHint(project, editor, message.get(), REFACTORING_NAME.get(), HelpID.EXTRACT_SUPERCLASS); - return; + @Override + @RequiredUIAccess + public boolean checkConflicts(ExtractSuperclassDialog dialog) { + MemberInfo[] infos = ArrayUtil.toObjectArray(dialog.getSelectedMemberInfos(), MemberInfo.class); + PsiDirectory targetDirectory = dialog.getTargetDirectory(); + PsiJavaPackage targetPackage; + if (targetDirectory != null) { + targetPackage = JavaDirectoryService.getInstance().getPackage(targetDirectory); + } + else { + targetPackage = null; + } + MultiMap conflicts = new MultiMap<>(); + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( + () -> { + PsiClass superClass = mySubclass.getExtendsListTypes().length > 0 ? mySubclass.getSuperClass() : null; + conflicts.putAllValues(PullUpConflictsUtil.checkConflicts( + infos, + mySubclass, + superClass, + targetPackage, + targetDirectory, + dialog.getContainmentVerifier(), + false + )); + }, + RefactoringLocalize.detectingPossibleConflicts(), + true, + myProject + )) { + return false; + } + ExtractSuperClassUtil.checkSuperAccessible(targetDirectory, conflicts, mySubclass); + return ExtractSuperClassUtil.showConflicts(dialog, conflicts, myProject); } - final List memberInfos = MemberInfo.extractClassMembers(mySubclass, element -> true, false); - - final ExtractSuperclassDialog dialog = - new ExtractSuperclassDialog(project, mySubclass, memberInfos, ExtractSuperclassHandler.this); - dialog.show(); - if (!dialog.isOK() || !dialog.isExtractSuperclass()) return; - - CommandProcessor.getInstance().executeCommand( - myProject, - () -> myProject.getApplication().runWriteAction(() -> doRefactoring(project, mySubclass, dialog)), - REFACTORING_NAME.get(), - null - ); - } - - public boolean checkConflicts(final ExtractSuperclassDialog dialog) { - final MemberInfo[] infos = ArrayUtil.toObjectArray(dialog.getSelectedMemberInfos(), MemberInfo.class); - final PsiDirectory targetDirectory = dialog.getTargetDirectory(); - final PsiJavaPackage targetPackage; - if (targetDirectory != null) { - targetPackage = JavaDirectoryService.getInstance().getPackage(targetDirectory); - } - else { - targetPackage = null; - } - final MultiMap conflicts = new MultiMap<>(); - if (!ProgressManager.getInstance().runProcessWithProgressSynchronously( - () -> { - final PsiClass superClass = mySubclass.getExtendsListTypes().length > 0 ? mySubclass.getSuperClass() : null; - conflicts.putAllValues(PullUpConflictsUtil.checkConflicts( - infos, - mySubclass, - superClass, - targetPackage, - targetDirectory, - dialog.getContainmentVerifier(), - false - )); - }, - RefactoringLocalize.detectingPossibleConflicts().get(), - true, - myProject - )) { - return false; + // invoked inside Command and Atomic action + @RequiredWriteAction + private void doRefactoring(Project project, PsiClass subclass, ExtractSuperclassDialog dialog) { + String superclassName = dialog.getExtractedSuperName(); + PsiDirectory targetDirectory = dialog.getTargetDirectory(); + MemberInfo[] selectedMemberInfos = ArrayUtil.toObjectArray(dialog.getSelectedMemberInfos(), MemberInfo.class); + DocCommentPolicy javaDocPolicy = new DocCommentPolicy(dialog.getDocCommentPolicy()); + LocalHistoryAction a = LocalHistory.getInstance().startAction(getCommandName(subclass, superclassName)); + try { + PsiClass superclass; + + try { + superclass = ExtractSuperClassUtil.extractSuperClass( + project, + targetDirectory, + superclassName, + subclass, + selectedMemberInfos, + javaDocPolicy + ); + } + finally { + a.finish(); + } + + // ask whether to search references to subclass and turn them into refs to superclass if possible + if (superclass != null) { + SmartPointerManager pointerManager = SmartPointerManager.getInstance(project); + SmartPsiElementPointer classPointer = pointerManager.createSmartPsiElementPointer(subclass); + SmartPsiElementPointer interfacePointer = pointerManager.createSmartPsiElementPointer(superclass); + SwingUtilities.invokeLater(() -> ExtractClassUtil.askAndTurnRefsToSuper(project, classPointer, interfacePointer)); + } + } + catch (IncorrectOperationException e) { + LOG.error(e); + } } - ExtractSuperClassUtil.checkSuperAccessible(targetDirectory, conflicts, mySubclass); - return ExtractSuperClassUtil.showConflicts(dialog, conflicts, myProject); - } - - // invoked inside Command and Atomic action - @RequiredWriteAction - private void doRefactoring(final Project project, final PsiClass subclass, final ExtractSuperclassDialog dialog) { - final String superclassName = dialog.getExtractedSuperName(); - final PsiDirectory targetDirectory = dialog.getTargetDirectory(); - final MemberInfo[] selectedMemberInfos = ArrayUtil.toObjectArray(dialog.getSelectedMemberInfos(), MemberInfo.class); - final DocCommentPolicy javaDocPolicy = new DocCommentPolicy(dialog.getDocCommentPolicy()); - LocalHistoryAction a = LocalHistory.getInstance().startAction(getCommandName(subclass, superclassName)); - try { - final PsiClass superclass; - - try { - superclass = - ExtractSuperClassUtil.extractSuperClass(project, targetDirectory, superclassName, subclass, selectedMemberInfos, javaDocPolicy); - } - finally { - a.finish(); - } - - // ask whether to search references to subclass and turn them into refs to superclass if possible - if (superclass != null) { - SmartPointerManager pointerManager = SmartPointerManager.getInstance(project); - final SmartPsiElementPointer classPointer = pointerManager.createSmartPsiElementPointer(subclass); - final SmartPsiElementPointer interfacePointer = pointerManager.createSmartPsiElementPointer(superclass); - SwingUtilities.invokeLater(() -> ExtractClassUtil.askAndTurnRefsToSuper(project, classPointer, interfacePointer)); - } - } - catch (IncorrectOperationException e) { - LOG.error(e); - } - } - @RequiredReadAction - private String getCommandName(final PsiClass subclass, String newName) { - return RefactoringLocalize.extractSuperclassCommandName(newName, DescriptiveNameUtil.getDescriptiveName(subclass)).get(); - } + @RequiredReadAction + private String getCommandName(PsiClass subclass, String newName) { + return RefactoringLocalize.extractSuperclassCommandName(newName, DescriptiveNameUtil.getDescriptiveName(subclass)).get(); + } - public boolean isEnabledOnElements(PsiElement[] elements) { - return elements.length == 1 && elements[0] instanceof PsiClass && !((PsiClass) elements[0]).isInterface() - &&!((PsiClass)elements[0]).isEnum(); - } + @Override + public boolean isEnabledOnElements(PsiElement[] elements) { + return elements.length == 1 && elements[0] instanceof PsiClass psiClass && !psiClass.isInterface() && !psiClass.isEnum(); + } }