From e7a8b1fa2aa42ca716f6bf61ca0c48b80881f31e Mon Sep 17 00:00:00 2001 From: Danila Ponomarenko Date: Fri, 4 May 2012 19:18:03 +0400 Subject: [PATCH] IDEA-20892 implemented --- .../analysis/HighlightControlFlowUtil.java | 3 +- .../InitializeFinalFieldInConstructorFix.java | 248 ++++++++++++++++++ .../src/messages/QuickFixBundle.properties | 3 + 3 files changed, 253 insertions(+), 1 deletion(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java index fd57237a8a6e..38c112b47e1b 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightControlFlowUtil.java @@ -288,6 +288,7 @@ public class HighlightControlFlowUtil { TextRange range = HighlightNamesUtil.getFieldDeclarationTextRange(field); final HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo(HighlightInfoType.ERROR, range.getStartOffset(), range.getEndOffset(), description); QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field)); + QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new InitializeFinalFieldInConstructorFix(field)); final PsiClass containingClass = field.getContainingClass(); if (containingClass != null && !containingClass.isInterface()) { IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(field, PsiModifier.FINAL, false, false); @@ -317,7 +318,7 @@ public class HighlightControlFlowUtil { if (scope instanceof PsiCodeBlock && scope.getParent() instanceof PsiSwitchStatement) { scope = PsiTreeUtil.getParentOfType(scope, PsiCodeBlock.class); } - + topBlock = JspPsiUtil.isInJspFile(scope) && scope instanceof PsiFile ? scope : PsiUtil.getTopLevelEnclosingCodeBlock(expression, scope); if (variable instanceof PsiField) { // non final field already initialized with default value diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java new file mode 100644 index 000000000000..ef316df2ef15 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/InitializeFinalFieldInConstructorFix.java @@ -0,0 +1,248 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInsight.daemon.impl.quickfix; + +import com.intellij.codeInsight.CodeInsightUtilBase; +import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.generation.PsiMethodMember; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.ide.util.MemberChooser; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.search.LocalSearchScope; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiTypesUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.refactoring.util.RefactoringUtil; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class InitializeFinalFieldInConstructorFix implements IntentionAction { + private final PsiField myField; + + public InitializeFinalFieldInConstructorFix(@NotNull PsiField field) { + myField = field; + } + + @NotNull + @Override + public String getText() { + return QuickFixBundle.message("initialize.final.field.in.constructor.name"); + } + + @NotNull + @Override + public String getFamilyName() { + return getText(); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + PsiClass containingClass = myField == null ? null : myField.getContainingClass(); + return myField != null + && myField.getManager().isInProject(myField) + && !myField.hasModifierProperty(PsiModifier.STATIC) + && myField.isValid() + && !myField.hasInitializer() + && containingClass != null + && containingClass.getName() != null; + } + + @Override + public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { + if (!CodeInsightUtilBase.prepareFileForWrite(file)) return; + + final PsiClass myClass = myField.getContainingClass(); + + if (myClass.getConstructors().length == 0) { + createDefaultConstructor(myClass, project, editor, file); + } + + final List constructors = choose(sort(filterIfFieldAlreadyAssigned(myField, myClass.getConstructors())), project); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + final List statements = addFieldInitialization(constructors, myField, project); + final PsiExpressionStatement highestStatement = getHighestElement(statements); + if (highestStatement == null) return; + + final PsiAssignmentExpression expression = (PsiAssignmentExpression)highestStatement.getExpression(); + final PsiElement rightExpression = expression.getRExpression(); + + final TextRange expressionRange = rightExpression.getTextRange(); + editor.getCaretModel().moveToOffset(expressionRange.getStartOffset()); + editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); + editor.getSelectionModel().setSelection(expressionRange.getStartOffset(), expressionRange.getEndOffset()); + } + }); + } + + @Nullable + private T getHighestElement(@NotNull List elements) { + T highest = null; + int highestTextOffset = Integer.MAX_VALUE; + for (T element : elements) { + final T forcedElem = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(element); + final int startOffset = forcedElem.getTextOffset(); + if (startOffset < highestTextOffset) { + highest = forcedElem; + highestTextOffset = startOffset; + } + } + return highest; + } + + @NotNull + private static List addFieldInitialization(@NotNull List constructors, + @NotNull PsiField field, + @NotNull Project project) { + final List statements = new ArrayList(); + for (PsiMethod constructor : constructors) { + final PsiExpressionStatement statement = addFieldInitialization(constructor, field, project); + if (statement != null) { + statements.add(statement); + } + } + return statements; + } + + @Nullable + private static PsiExpressionStatement addFieldInitialization(@NotNull PsiMethod constructor, + @NotNull PsiField field, + @NotNull Project project) { + PsiCodeBlock methodBody = constructor.getBody(); + if (methodBody == null) return null; + + final String fieldName = field.getName(); + String stmtText = fieldName + " = " + suggestInitValue(field) + ";"; + if (methodContainsParameterWithName(constructor, fieldName)) { + stmtText = "this." + stmtText; + } + + final PsiManager psiManager = PsiManager.getInstance(project); + final PsiElementFactory factory = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory(); + final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + + return (PsiExpressionStatement)methodBody.add(codeStyleManager.reformat(factory.createStatementFromText(stmtText, methodBody))); + } + + 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 choose(@NotNull PsiMethod[] ctors, @NotNull final Project project) { + if (ctors.length == 1) { + return Arrays.asList(ctors[0]); + } + + if (ctors.length > 1) { + final MemberChooser chooser = new MemberChooser(toPsiMethodMemberArray(ctors), false, true, project); + chooser.setTitle(QuickFixBundle.message("initialize.final.field.in.constructor.choose.dialog.title")); + chooser.show(); + + final List chosenMembers = chooser.getSelectedElements(); + if (chosenMembers != null) { + return Arrays.asList(toPsiMethodArray(chosenMembers)); + } + } + + return Collections.emptyList(); + } + + private static PsiMethodMember[] toPsiMethodMemberArray(@NotNull PsiMethod[] 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[] toPsiMethodArray(@NotNull List methodMembers) { + final PsiMethod[] result = new PsiMethod[methodMembers.size()]; + int i = 0; + for (PsiMethodMember methodMember : methodMembers) { + result[i++] = methodMember.getElement(); + } + return result; + } + + private static void createDefaultConstructor(PsiClass psiClass, @NotNull final Project project, final Editor editor, final PsiFile file) { + final AddDefaultConstructorFix defaultConstructorFix = new AddDefaultConstructorFix(psiClass); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + defaultConstructorFix.invoke(project, editor, file); + } + }); + } + + private static PsiMethod[] filterIfFieldAlreadyAssigned(@NotNull PsiField field, @NotNull PsiMethod[] ctors) { + final List result = new ArrayList(Arrays.asList(ctors)); + for (PsiReference reference : ReferencesSearch.search(field, new LocalSearchScope(ctors))) { + final PsiElement element = reference.getElement(); + if (element instanceof PsiReferenceExpression && PsiUtil.isOnAssignmentLeftHand((PsiExpression)element)) { + result.remove(PsiTreeUtil.getParentOfType(element, PsiMethod.class)); + } + } + return result.toArray(new PsiMethod[result.size()]); + } + + @NotNull + private static PsiMethod[] sort(@NotNull PsiMethod[] ctors) { + Arrays.sort(ctors, new Comparator() { + @Override + public int compare(PsiMethod c1, PsiMethod c2) { + final PsiMethod cc1 = RefactoringUtil.getChainedConstructor(c1); + final PsiMethod cc2 = RefactoringUtil.getChainedConstructor(c2); + if (cc1 == c2) return 1; + if (cc2 == c1) return -1; + if (cc1 == null) { + return cc2 == null ? 0 : compare(c1, cc2); + } else { + return cc2 == null ? compare(cc1, c2) : compare(cc1, cc2); + } + } + }); + + return ctors; + } + + private static String suggestInitValue(@NotNull PsiField field) { + PsiType type = field.getType(); + return PsiTypesUtil.getDefaultValueOfType(type); + } + + @Override + public boolean startInWriteAction() { + return false; + } +} diff --git a/resources-en/src/messages/QuickFixBundle.properties b/resources-en/src/messages/QuickFixBundle.properties index d951b419d983..aec410a7e2b3 100644 --- a/resources-en/src/messages/QuickFixBundle.properties +++ b/resources-en/src/messages/QuickFixBundle.properties @@ -261,3 +261,6 @@ change.to.append.text=Change to ''{0}'' 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 \ No newline at end of file