[java-intentions] InitializeFinalFieldInConstructorFix: ModCommand

GitOrigin-RevId: 2fafef8e7f7c086bfbadc0d7e1470359f5c677e9
This commit is contained in:
Tagir Valeev
2023-07-13 15:19:34 +02:00
committed by intellij-monorepo-bot
parent 9f044ceb21
commit e8adfcce9b
5 changed files with 52 additions and 138 deletions

View File

@@ -291,7 +291,7 @@ convert.to.string.family=Fix character literal
convert.to.string.text=Convert to string literal
initialize.final.field.in.constructor.name=Initialize in constructor
initialize.final.field.in.constructor.choose.dialog.title=Choose Constructors to Add Initialization to
initialize.final.field.in.constructor.choose.dialog.title=Choose Constructors to Add Initialization To
remove.redundant.arguments.text=Remove redundant arguments to call ''{0}''
remove.redundant.arguments.family=Remove redundant arguments

View File

@@ -3,32 +3,26 @@ package com.intellij.codeInsight.daemon.impl.quickfix;
import com.intellij.codeInsight.daemon.QuickFixBundle;
import com.intellij.codeInsight.generation.PsiMethodMember;
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.template.PsiElementResult;
import com.intellij.codeInsight.template.TemplateBuilderFactory;
import com.intellij.codeInsight.template.TemplateBuilderImpl;
import com.intellij.codeInsight.template.impl.ConstantNode;
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.ide.util.MemberChooser;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.codeInspection.ModCommands;
import com.intellij.modcommand.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Computable;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class InitializeFinalFieldInConstructorFix extends LocalQuickFixAndIntentionActionOnPsiElement {
public class InitializeFinalFieldInConstructorFix extends PsiBasedModCommandAction<PsiField> {
private static final Logger LOG = Logger.getInstance(InitializeFinalFieldInConstructorFix.class);
public InitializeFinalFieldInConstructorFix(@NotNull PsiField field) {
@@ -37,81 +31,59 @@ public class InitializeFinalFieldInConstructorFix extends LocalQuickFixAndIntent
@NotNull
@Override
public String getText() {
public String getFamilyName() {
return QuickFixBundle.message("initialize.final.field.in.constructor.name");
}
@NotNull
@Override
public String getFamilyName() {
return getText();
}
@Override
public boolean isAvailable(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
PsiField field = ObjectUtils.tryCast(startElement, PsiField.class);
if (field == null) return false;
if (!field.isValid() || field.hasModifierProperty(PsiModifier.STATIC) || field.hasInitializer()) {
return false;
}
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiField field) {
if (field.hasModifierProperty(PsiModifier.STATIC) || field.hasInitializer()) return null;
final PsiClass containingClass = field.getContainingClass();
if (containingClass == null || containingClass.getName() == null){
return false;
}
if (containingClass == null || containingClass.getName() == null) return null;
final PsiManager manager = field.getManager();
return manager != null && BaseIntentionAction.canModify(field);
return Presentation.of(getFamilyName());
}
@Override
public void invoke(@NotNull Project project,
@NotNull PsiFile file,
@Nullable Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
PsiField field = ObjectUtils.tryCast(startElement, PsiField.class);
if (field == null) return;
protected @NotNull ModCommand perform(@NotNull ActionContext context, @NotNull PsiField field) {
final PsiClass myClass = field.getContainingClass();
if (myClass == null) return;
if (myClass == null) return ModCommands.nop();
if (myClass.getConstructors().length == 0) {
ApplicationManager.getApplication().runWriteAction(
(Computable<PsiMethod>)() -> AddDefaultConstructorFix.addDefaultConstructor(myClass));
return ModCommands.psiUpdate(context, updater -> {
PsiClass writableClass = updater.getWritable(myClass);
PsiField writableField = updater.getWritable(field);
PsiMethod ctor = AddDefaultConstructorFix.addDefaultConstructor(writableClass);
addFieldInitialization(List.of(ctor), writableField, updater);
});
}
PsiMethod[] ctors = CreateConstructorParameterFromFieldFix.filterConstructorsIfFieldAlreadyAssigned(myClass.getConstructors(), field)
.toArray(PsiMethod.EMPTY_ARRAY);
final List<PsiMethod> constructors = choose(ctors, project);
List<PsiMethod> ctors =
CreateConstructorParameterFromFieldFix.filterConstructorsIfFieldAlreadyAssigned(myClass.getConstructors(), field);
ApplicationManager.getApplication().runWriteAction(() -> addFieldInitialization(constructors, field, project, editor));
List<PsiMethodMember> allMembers = ContainerUtil.map(ctors, PsiMethodMember::new);
if (ctors.size() == 1) {
return getFinalCommand(field, allMembers);
}
return new ModChooseMember(QuickFixBundle.message("initialize.final.field.in.constructor.choose.dialog.title"),
allMembers, allMembers, ModChooseMember.SelectionMode.MULTIPLE,
chosenMembers -> getFinalCommand(field, chosenMembers));
}
@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
PsiField field = PsiTreeUtil.findSameElementInCopy(ObjectUtils.tryCast(getStartElement(), PsiField.class), file);
if (field == null) return IntentionPreviewInfo.EMPTY;
final PsiClass myClass = field.getContainingClass();
if (myClass == null) return IntentionPreviewInfo.EMPTY;
if (myClass.getConstructors().length == 0) {
AddDefaultConstructorFix.addDefaultConstructor(myClass);
}
PsiMethod[] ctors = CreateConstructorParameterFromFieldFix.filterConstructorsIfFieldAlreadyAssigned(myClass.getConstructors(), field)
.toArray(PsiMethod.EMPTY_ARRAY);
final List<PsiMethod> constructors = Arrays.asList(ctors);
addFieldInitialization(constructors, field, project, editor);
return IntentionPreviewInfo.DIFF;
@NotNull
private static ModCommand getFinalCommand(@NotNull PsiField field, List<? extends @NotNull MemberChooserElement> chosenMembers) {
return ModCommands.psiUpdate(field, (writableField, updater) -> {
List<PsiMethod> writableConstructors =
ContainerUtil.map(chosenMembers, member -> updater.getWritable(((PsiMethodMember)member).getElement()));
addFieldInitialization(writableConstructors, writableField, updater);
});
}
private static void addFieldInitialization(@NotNull List<? extends PsiMethod> constructors,
@NotNull PsiField field,
@NotNull Project project,
@Nullable Editor editor) {
@NotNull ModPsiUpdater updater) {
Project project = field.getProject();
if (constructors.isEmpty()) return;
final LookupElement[] suggestedInitializers = AddVariableInitializerFix.suggestInitializer(field);
@@ -124,22 +96,21 @@ public class InitializeFinalFieldInConstructorFix extends LocalQuickFixAndIntent
Document doc = Objects.requireNonNull(field.getContainingFile().getViewProvider().getDocument());
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(doc);
List<PsiExpression> rExpressions = ContainerUtil.mapNotNull(rExprPointers, SmartPsiElementPointer::getElement);
runAssignmentTemplate(rExpressions, suggestedInitializers, editor);
runAssignmentTemplate(rExpressions, suggestedInitializers, updater);
}
private static void runAssignmentTemplate(@NotNull final List<? extends PsiExpression> initializers,
final LookupElement @NotNull [] suggestedInitializers,
@Nullable Editor editor) {
if (editor == null) return;
final LookupElement @NotNull [] suggestedInitializers,
@NotNull ModPsiUpdater updater) {
LOG.assertTrue(!initializers.isEmpty());
final PsiExpression initializer = Objects.requireNonNull(ContainerUtil.getFirstItem(initializers));
PsiElement context = initializers.size() == 1 ? initializer : PsiTreeUtil.findCommonParent(initializers);
if (context == null) return;
final TemplateBuilderImpl builder = (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(context);
ModTemplateBuilder builder = updater.templateBuilder();
for (PsiExpression e : initializers) {
builder.replaceElement(e, new ConstantNode(new PsiElementResult(suggestedInitializers[0].getPsiElement())).withLookupItems(suggestedInitializers));
builder.field(e, new ConstantNode(new PsiElementResult(suggestedInitializers[0].getPsiElement())).withLookupItems(
suggestedInitializers));
}
builder.run(editor, false);
}
@NotNull
@@ -156,7 +127,7 @@ public class InitializeFinalFieldInConstructorFix extends LocalQuickFixAndIntent
}
final String fieldName = field.getName();
String stmtText = fieldName + " = " + suggestedInitializers[0].getPsiElement().getText() + ";";
String stmtText = fieldName + " = " + Objects.requireNonNull(suggestedInitializers[0].getPsiElement()).getText() + ";";
if (methodContainsParameterWithName(constructor, fieldName)) {
stmtText = "this." + stmtText;
}
@@ -171,63 +142,6 @@ public class InitializeFinalFieldInConstructorFix extends LocalQuickFixAndIntent
}
private static boolean methodContainsParameterWithName(@NotNull PsiMethod constructor, @NotNull String name) {
for (PsiParameter parameter : constructor.getParameterList().getParameters()) {
if (name.equals(parameter.getName())) {
return true;
}
}
return false;
}
@NotNull
private static List<PsiMethod> choose(PsiMethod @NotNull [] ctors, @NotNull final Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return Arrays.asList(ctors);
}
if (ctors.length == 1) {
return Collections.singletonList(ctors[0]);
}
if (ctors.length > 1) {
final MemberChooser<PsiMethodMember> chooser = new MemberChooser<>(toPsiMethodMemberArray(ctors), false, true, project);
chooser.setTitle(QuickFixBundle.message("initialize.final.field.in.constructor.choose.dialog.title"));
chooser.show();
final List<PsiMethodMember> chosenMembers = chooser.getSelectedElements();
if (chosenMembers != null) {
return Arrays.asList(toPsiMethodArray(chosenMembers));
}
}
return Collections.emptyList();
}
private static PsiMethodMember @NotNull [] toPsiMethodMemberArray(PsiMethod @NotNull [] methods) {
final PsiMethodMember[] result = new PsiMethodMember[methods.length];
for (int i = 0; i < methods.length; i++) {
result[i] = new PsiMethodMember(methods[i]);
}
return result;
}
private static PsiMethod @NotNull [] toPsiMethodArray(@NotNull List<? extends PsiMethodMember> methodMembers) {
final PsiMethod[] result = new PsiMethod[methodMembers.size()];
int i = 0;
for (PsiMethodMember methodMember : methodMembers) {
result[i++] = methodMember.getElement();
}
return result;
}
@Nullable
@Override
public PsiElement getElementToMakeWritable(@NotNull PsiFile currentFile) {
return currentFile;
}
@Override
public boolean startInWriteAction() {
return false;
return ContainerUtil.exists(constructor.getParameterList().getParameters(), parameter -> name.equals(parameter.getName()));
}
}

View File

@@ -606,7 +606,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
@NotNull
@Override
public IntentionAction createInitializeFinalFieldInConstructorFix(@NotNull PsiField field) {
return new InitializeFinalFieldInConstructorFix(field);
return new InitializeFinalFieldInConstructorFix(field).asIntention();
}
@NotNull

View File

@@ -82,7 +82,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
fixes.add(QuickFixFactory.getInstance().createDeleteFix(annotation, JavaBundle.message("quickfix.text.remove.not.null.annotation")));
}
if (isOnTheFly) {
fixes.add(new InitializeFinalFieldInConstructorFix(field));
fixes.add(new InitializeFinalFieldInConstructorFix(field).asQuickFix());
fixes.add(new AddVariableInitializerFix(field).asQuickFix());
}

View File

@@ -7,6 +7,6 @@ class Main {
}
private Main(int var) {
this.var = 0;
this.var = 0<caret>;
}
}<caret>
}