blob: b8097d7376f6d1bec436e5056fbf5a52651012f2 [file] [log] [blame]
/*
* Copyright 2000-2009 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.refactoring.inline;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.sameParameterValue.SameParameterValueInspection;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.controlFlow.DefUseUtil;
import com.intellij.psi.search.searches.OverridingMethodsSearch;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.listeners.RefactoringEventData;
import com.intellij.refactoring.listeners.RefactoringEventListener;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.InlineUtil;
import com.intellij.refactoring.util.RefactoringMessageDialog;
import com.intellij.util.Processor;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author yole
*/
public class InlineParameterHandler extends JavaInlineActionHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.inline.InlineParameterHandler");
public static final String REFACTORING_NAME = RefactoringBundle.message("inline.parameter.refactoring");
public static final String REFACTORING_ID = "refactoring.inline.parameter";
public boolean canInlineElement(PsiElement element) {
if (element instanceof PsiParameter) {
final PsiElement parent = element.getParent();
if (parent instanceof PsiParameterList &&
parent.getParent() instanceof PsiMethod &&
element.getLanguage() == JavaLanguage.INSTANCE) {
return true;
}
}
return false;
}
public void inlineElement(final Project project, final Editor editor, final PsiElement psiElement) {
final PsiParameter psiParameter = (PsiParameter) psiElement;
final PsiParameterList parameterList = (PsiParameterList) psiParameter.getParent();
if (!(parameterList.getParent() instanceof PsiMethod)) {
return;
}
final int index = parameterList.getParameterIndex(psiParameter);
final PsiMethod method = (PsiMethod) parameterList.getParent();
String errorMessage = getCannotInlineMessage(psiParameter, method);
if (errorMessage != null) {
CommonRefactoringUtil.showErrorHint(project, editor, errorMessage, RefactoringBundle.message("inline.parameter.refactoring"), null);
return;
}
final Ref<PsiExpression> refInitializer = new Ref<PsiExpression>();
final Ref<PsiExpression> refConstantInitializer = new Ref<PsiExpression>();
final Ref<PsiCallExpression> refMethodCall = new Ref<PsiCallExpression>();
final List<PsiReference> occurrences = Collections.synchronizedList(new ArrayList<PsiReference>());
final Collection<PsiFile> containingFiles = Collections.synchronizedSet(new HashSet<PsiFile>());
containingFiles.add(psiParameter.getContainingFile());
boolean result = ReferencesSearch.search(method).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference psiReference) {
PsiElement element = psiReference.getElement();
final PsiElement parent = element.getParent();
if (parent instanceof PsiCallExpression) {
final PsiCallExpression methodCall = (PsiCallExpression)parent;
occurrences.add(psiReference);
containingFiles.add(element.getContainingFile());
final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions();
if (expressions.length <= index) return false;
PsiExpression argument = expressions[index];
if (!refInitializer.isNull()) {
return argument != null
&& PsiEquivalenceUtil.areElementsEquivalent(refInitializer.get(), argument)
&& PsiEquivalenceUtil.areElementsEquivalent(refMethodCall.get(), methodCall);
}
if (InlineToAnonymousConstructorProcessor.isConstant(argument) || getReferencedFinalField(argument) != null) {
if (refConstantInitializer.isNull()) {
refConstantInitializer.set(argument);
}
else if (!isSameConstant(argument, refConstantInitializer.get())) {
return false;
}
} else if (!isRecursiveReferencedParameter(argument, psiParameter)) {
if (!refConstantInitializer.isNull()) return false;
refInitializer.set(argument);
refMethodCall.set(methodCall);
}
}
return true;
}
});
final int offset = editor.getCaretModel().getOffset();
final PsiElement refExpr = psiElement.getContainingFile().findElementAt(offset);
final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(refExpr, PsiCodeBlock.class);
if (codeBlock != null) {
final PsiElement[] defs = DefUseUtil.getDefs(codeBlock, psiParameter, refExpr);
if (defs.length == 1) {
final PsiElement def = defs[0];
if (def instanceof PsiReferenceExpression && PsiUtil.isOnAssignmentLeftHand((PsiExpression)def)) {
final PsiExpression rExpr = ((PsiAssignmentExpression)def.getParent()).getRExpression();
if (rExpr != null) {
final PsiElement[] refs = DefUseUtil.getRefs(codeBlock, psiParameter, refExpr);
if (InlineLocalHandler.checkRefsInAugmentedAssignmentOrUnaryModified(refs, def) == null) {
new WriteCommandAction(project) {
@Override
protected void run(Result result) throws Throwable {
for (final PsiElement ref : refs) {
InlineUtil.inlineVariable(psiParameter, rExpr, (PsiJavaCodeReferenceElement)ref);
}
def.getParent().delete();
}
}.execute();
return;
}
}
}
}
}
if (occurrences.isEmpty()) {
CommonRefactoringUtil
.showErrorHint(project, editor, "Method has no usages", RefactoringBundle.message("inline.parameter.refactoring"), null);
return;
}
if (!result) {
CommonRefactoringUtil.showErrorHint(project, editor, "Cannot find constant initializer for parameter", RefactoringBundle.message("inline.parameter.refactoring"), null);
return;
}
if (!refInitializer.isNull()) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
final InlineParameterExpressionProcessor processor =
new InlineParameterExpressionProcessor(refMethodCall.get(), method, psiParameter, refInitializer.get(),
method.getProject().getUserData(
InlineParameterExpressionProcessor.CREATE_LOCAL_FOR_TESTS));
processor.run();
}
else {
final boolean createLocal = ReferencesSearch.search(psiParameter).findAll().size() > 1;
InlineParameterDialog dlg = new InlineParameterDialog(refMethodCall.get(), method, psiParameter, refInitializer.get(), createLocal);
dlg.show();
}
return;
}
if (refConstantInitializer.isNull()) {
CommonRefactoringUtil.showErrorHint(project, editor, "Cannot find constant initializer for parameter", RefactoringBundle.message("inline.parameter.refactoring"), null);
return;
}
final Ref<Boolean> isNotConstantAccessible = new Ref<Boolean>();
final PsiExpression constantExpression = refConstantInitializer.get();
constantExpression.accept(new JavaRecursiveElementVisitor(){
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final PsiElement resolved = expression.resolve();
if (resolved instanceof PsiMember && !PsiUtil.isAccessible((PsiMember)resolved, method, null)) {
isNotConstantAccessible.set(Boolean.TRUE);
}
}
});
if (!isNotConstantAccessible.isNull() && isNotConstantAccessible.get()) {
CommonRefactoringUtil.showErrorHint(project, editor, "Constant initializer is not accessible in method body", RefactoringBundle.message("inline.parameter.refactoring"), null);
return;
}
for (PsiReference psiReference : ReferencesSearch.search(psiParameter)) {
final PsiElement element = psiReference.getElement();
if (element instanceof PsiExpression && PsiUtil.isAccessedForWriting((PsiExpression)element)) {
CommonRefactoringUtil.showErrorHint(project, editor, "Inline parameter which has write usages is not supported", RefactoringBundle.message("inline.parameter.refactoring"), null);
return;
}
}
if (!ApplicationManager.getApplication().isUnitTestMode()) {
String occurencesString = RefactoringBundle.message("occurrences.string", occurrences.size());
String question = RefactoringBundle.message("inline.parameter.confirmation", psiParameter.getName(),
constantExpression.getText()) + " " + occurencesString;
RefactoringMessageDialog dialog = new RefactoringMessageDialog(
REFACTORING_NAME,
question,
HelpID.INLINE_VARIABLE,
"OptionPane.questionIcon",
true,
project);
dialog.show();
if (!dialog.isOK()){
return;
}
}
final RefactoringEventData data = new RefactoringEventData();
data.addElement(psiElement.copy());
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
project.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringStarted(REFACTORING_ID, data);
SameParameterValueInspection.InlineParameterValueFix.inlineSameParameterValue(method, psiParameter, constantExpression);
project.getMessageBus().syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC).refactoringDone(REFACTORING_ID, null);
}
}, REFACTORING_NAME, null);
}
@Nullable
private static PsiField getReferencedFinalField(final PsiExpression argument) {
if (argument instanceof PsiReferenceExpression) {
final PsiElement element = ((PsiReferenceExpression)argument).resolve();
if (element instanceof PsiField) {
final PsiField field = (PsiField)element;
final PsiModifierList modifierList = field.getModifierList();
if (modifierList != null && modifierList.hasModifierProperty(PsiModifier.FINAL)) {
return field;
}
}
}
return null;
}
private static boolean isRecursiveReferencedParameter(final PsiExpression argument, final PsiParameter param) {
if (argument instanceof PsiReferenceExpression) {
final PsiElement element = ((PsiReferenceExpression)argument).resolve();
if (element instanceof PsiParameter) {
return element.equals(param);
}
}
return false;
}
private static boolean isSameConstant(final PsiExpression expr1, final PsiExpression expr2) {
boolean expr1Null = InlineToAnonymousConstructorProcessor.ourNullPattern.accepts(expr1);
boolean expr2Null = InlineToAnonymousConstructorProcessor.ourNullPattern.accepts(expr2);
if (expr1Null || expr2Null) {
return expr1Null && expr2Null;
}
final PsiField field1 = getReferencedFinalField(expr1);
final PsiField field2 = getReferencedFinalField(expr2);
if (field1 != null && field1 == field2) {
return true;
}
Object value1 = JavaPsiFacade.getInstance(expr1.getProject()).getConstantEvaluationHelper().computeConstantExpression(expr1);
Object value2 = JavaPsiFacade.getInstance(expr2.getProject()).getConstantEvaluationHelper().computeConstantExpression(expr2);
return value1 != null && value2 != null && value1.equals(value2);
}
@Nullable
private static String getCannotInlineMessage(final PsiParameter psiParameter, final PsiMethod method) {
if (psiParameter.isVarArgs()) {
return RefactoringBundle.message("inline.parameter.error.varargs");
}
if (method.findSuperMethods().length > 0 ||
OverridingMethodsSearch.search(method, true).toArray(PsiMethod.EMPTY_ARRAY).length > 0) {
return RefactoringBundle.message("inline.parameter.error.hierarchy");
}
return null;
}
}