blob: ea5e3641cb342f02c6ccda7fb718b4115e99689e [file] [log] [blame]
/*
* 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.CodeInsightUtilCore;
import com.intellij.codeInsight.FileModificationService;
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.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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) {
if (!myField.isValid() || myField.hasModifierProperty(PsiModifier.STATIC) || myField.hasInitializer()) {
return false;
}
final PsiClass containingClass = myField.getContainingClass();
if (containingClass == null || containingClass.getName() == null){
return false;
}
final PsiManager manager = myField.getManager();
return manager != null && manager.isInProject(myField);
}
@Override
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
final PsiClass myClass = myField.getContainingClass();
if (myClass == null) {
return;
}
if (myClass.getConstructors().length == 0) {
createDefaultConstructor(myClass, project, editor, file);
}
final List<PsiMethod> constructors = choose(filterIfFieldAlreadyAssigned(myField, myClass.getConstructors()), project);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
final List<PsiExpressionStatement> 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 static <T extends PsiElement> T getHighestElement(@NotNull List<T> elements) {
T highest = null;
int highestTextOffset = Integer.MAX_VALUE;
for (T element : elements) {
final T forcedElem = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(element);
final int startOffset = forcedElem.getTextOffset();
if (startOffset < highestTextOffset) {
highest = forcedElem;
highestTextOffset = startOffset;
}
}
return highest;
}
@NotNull
private static List<PsiExpressionStatement> addFieldInitialization(@NotNull List<PsiMethod> constructors,
@NotNull PsiField field,
@NotNull Project project) {
final List<PsiExpressionStatement> statements = new ArrayList<PsiExpressionStatement>();
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<PsiMethod> choose(@NotNull PsiMethod[] ctors, @NotNull final Project project) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
return Arrays.asList(ctors);
}
if (ctors.length == 1) {
return Arrays.asList(ctors[0]);
}
if (ctors.length > 1) {
final MemberChooser<PsiMethodMember> chooser = new MemberChooser<PsiMethodMember>(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[] 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<PsiMethodMember> 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<PsiMethod> result = new ArrayList<PsiMethod>(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()]);
}
private static String suggestInitValue(@NotNull PsiField field) {
PsiType type = field.getType();
return PsiTypesUtil.getDefaultValueOfType(type);
}
@Override
public boolean startInWriteAction() {
return false;
}
}