inline superclass: non empty super constructor - field initialization (IDEADEV-40788)

This commit is contained in:
anna
2009-10-12 15:53:57 +04:00
parent 5f03ff5849
commit d7af310c3a
17 changed files with 274 additions and 58 deletions

View File

@@ -18,23 +18,17 @@ package com.intellij.refactoring.inline;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.InlineUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.Processor;
import com.intellij.lang.StdLanguages;
import java.util.ArrayList;
import java.util.List;
class InlineMethodHandler extends JavaInlineActionHandler {
private static final String REFACTORING_NAME = RefactoringBundle.message("inline.method.title");
@@ -61,8 +55,8 @@ class InlineMethodHandler extends JavaInlineActionHandler {
PsiReference reference = editor != null ? TargetElementUtilBase.findReference(editor, editor.getCaretModel().getOffset()) : null;
boolean allowInlineThisOnly = false;
if (InlineMethodProcessor.checkBadReturns(method) && !allUsagesAreTailCalls(method)) {
if (reference != null && getTailCallType(reference) != TailCallType.None) {
if (InlineMethodProcessor.checkBadReturns(method) && !InlineUtil.allUsagesAreTailCalls(method)) {
if (reference != null && InlineUtil.getTailCallType(reference) != InlineUtil.TailCallType.None) {
allowInlineThisOnly = true;
}
else {
@@ -111,45 +105,6 @@ class InlineMethodHandler extends JavaInlineActionHandler {
dialog.show();
}
public static boolean allUsagesAreTailCalls(final PsiMethod method) {
final List<PsiReference> nonTailCallUsages = new ArrayList<PsiReference>();
boolean result = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
public void run() {
ReferencesSearch.search(method).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference psiReference) {
ProgressManager.getInstance().checkCanceled();
if (getTailCallType(psiReference) == TailCallType.None) {
nonTailCallUsages.add(psiReference);
return false;
}
return true;
}
});
}
}, RefactoringBundle.message("inline.method.checking.tail.calls.progress"), true, method.getProject());
return result && nonTailCallUsages.isEmpty();
}
public enum TailCallType {
None, Simple, Return
}
public static TailCallType getTailCallType(final PsiReference psiReference) {
PsiElement element = psiReference.getElement();
PsiExpression methodCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (methodCall == null) return TailCallType.None;
if (methodCall.getParent() instanceof PsiReturnStatement) return TailCallType.Return;
if (methodCall.getParent() instanceof PsiExpressionStatement) {
PsiStatement callStatement = (PsiStatement) methodCall.getParent();
PsiMethod callerMethod = PsiTreeUtil.getParentOfType(callStatement, PsiMethod.class);
if (callerMethod != null) {
final PsiStatement[] psiStatements = callerMethod.getBody().getStatements();
return psiStatements.length > 0 && callStatement == psiStatements [psiStatements.length-1] ? TailCallType.Simple : TailCallType.None;
}
}
return TailCallType.None;
}
public static boolean isChainingConstructor(PsiMethod constructor) {
PsiCodeBlock body = constructor.getBody();
if (body != null) {

View File

@@ -342,7 +342,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
private void inlineMethodCall(PsiReferenceExpression ref) throws IncorrectOperationException {
InlineMethodHandler.TailCallType tailCall = InlineMethodHandler.getTailCallType(ref);
InlineUtil.TailCallType tailCall = InlineUtil.getTailCallType(ref);
ChangeContextUtil.encodeContextInfo(myMethod, false);
myMethodCopy = (PsiMethod)myMethod.copy();
ChangeContextUtil.clearContextInfo(myMethod);
@@ -379,7 +379,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
PsiElement last = statements[statements.length - 1];*/
if (statements.length > 0 && statements[statements.length - 1] instanceof PsiReturnStatement &&
tailCall != InlineMethodHandler.TailCallType.Return) {
tailCall != InlineUtil.TailCallType.Return) {
last--;
}
@@ -411,7 +411,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
if (statements.length > 0) {
final PsiStatement lastStatement = statements[statements.length - 1];
if (lastStatement instanceof PsiReturnStatement && tailCall != InlineMethodHandler.TailCallType.Return) {
if (lastStatement instanceof PsiReturnStatement && tailCall != InlineUtil.TailCallType.Return) {
final PsiExpression returnValue = ((PsiReturnStatement)lastStatement).getReturnValue();
if (returnValue != null && PsiUtil.isStatement(returnValue)) {
PsiExpressionStatement exprStatement = (PsiExpressionStatement)myFactory.createStatementFromText("a;", null);
@@ -422,7 +422,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
}
if (methodCall.getParent() instanceof PsiExpressionStatement || tailCall == InlineMethodHandler.TailCallType.Return) {
if (methodCall.getParent() instanceof PsiExpressionStatement || tailCall == InlineUtil.TailCallType.Return) {
methodCall.getParent().delete();
}
else {
@@ -533,7 +533,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
private BlockData prepareBlock(PsiReferenceExpression ref,
final PsiSubstitutor callSubstitutor,
final PsiExpressionList argumentList,
final InlineMethodHandler.TailCallType tailCallType)
final InlineUtil.TailCallType tailCallType)
throws IncorrectOperationException {
final PsiCodeBlock block = myMethodCopy.getBody();
final PsiStatement[] originalStatements = block.getStatements();
@@ -542,7 +542,7 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
PsiType returnType = callSubstitutor.substitute(myMethod.getReturnType());
String resultName = null;
final int applicabilityLevel = PsiUtil.getApplicabilityLevel(myMethod, callSubstitutor, argumentList);
if (returnType != null && returnType != PsiType.VOID && tailCallType == InlineMethodHandler.TailCallType.None) {
if (returnType != null && returnType != PsiType.VOID && tailCallType == InlineUtil.TailCallType.None) {
resultName = myJavaCodeStyle.propertyNameToVariableName("result", VariableKind.LOCAL_VARIABLE);
resultName = myJavaCodeStyle.suggestUniqueVariableName(resultName, block.getFirstChild(), true);
PsiDeclarationStatement declaration = myFactory.createVariableDeclarationStatement(resultName, returnType, null);
@@ -615,13 +615,13 @@ public class InlineMethodProcessor extends BaseRefactoringProcessor {
}
}
if (resultName != null || tailCallType == InlineMethodHandler.TailCallType.Simple) {
if (resultName != null || tailCallType == InlineUtil.TailCallType.Simple) {
PsiReturnStatement[] returnStatements = RefactoringUtil.findReturnStatements(myMethodCopy);
for (PsiReturnStatement returnStatement : returnStatements) {
final PsiExpression returnValue = returnStatement.getReturnValue();
if (returnValue == null) continue;
PsiStatement statement;
if (tailCallType == InlineMethodHandler.TailCallType.Simple) {
if (tailCallType == InlineUtil.TailCallType.Simple) {
if (returnValue instanceof PsiCallExpression) {
PsiExpressionStatement exprStatement = (PsiExpressionStatement) myFactory.createStatementFromText("a;", null);
exprStatement.getExpression().replace(returnValue);

View File

@@ -23,7 +23,7 @@ import java.util.HashSet;
/**
* @author ven
*/
class ReferencedElementsCollector extends JavaRecursiveElementWalkingVisitor {
public class ReferencedElementsCollector extends JavaRecursiveElementWalkingVisitor {
final HashSet<PsiMember> myReferencedMembers = new HashSet<PsiMember>();
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {

View File

@@ -142,6 +142,26 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori
}
}
}
for (PsiMethod constructor : targetClass.getConstructors()) {
final PsiCodeBlock constrBody = constructor.getBody();
LOG.assertTrue(constrBody != null);
final PsiStatement[] statements = constrBody.getStatements();
if (statements.length > 0) {
final PsiStatement firstConstrStatement = statements[0];
if (firstConstrStatement instanceof PsiExpressionStatement) {
final PsiExpression expression = ((PsiExpressionStatement)firstConstrStatement).getExpression();
if (expression instanceof PsiMethodCallExpression) {
final PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)expression).getMethodExpression();
if (methodExpression.getText().equals(PsiKeyword.SUPER)) {
final PsiMethod superConstructor = ((PsiMethodCallExpression)expression).resolveMethod();
if (superConstructor != null && superConstructor.getBody() != null) {
usages.add(new InlineSuperCallUsageInfo((PsiMethodCallExpression)expression));
}
}
}
}
}
}
}
}
@@ -154,6 +174,7 @@ public class InlineSuperClassRefactoringProcessor extends FixableUsagesRefactori
final PsiMember member = info.getMember();
pushDownConflicts.checkMemberPlacementInTargetClassConflict(targetClass, member);
}
//todo check accessibility conflicts
}
for (PsiElement element : pushDownConflicts.getConflicts().keySet()) {
conflicts.put(element, pushDownConflicts.getConflicts().get(element));

View File

@@ -0,0 +1,94 @@
/*
* 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.
*/
/*
* User: anna
* Date: 10-Oct-2009
*/
package com.intellij.refactoring.inlineSuperClass.usageInfo;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.inline.InlineMethodProcessor;
import com.intellij.refactoring.inline.ReferencedElementsCollector;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.FixableUsageInfo;
import com.intellij.refactoring.util.InlineUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
public class InlineSuperCallUsageInfo extends FixableUsageInfo {
public InlineSuperCallUsageInfo(PsiMethodCallExpression methodCallExpression) {
super(methodCallExpression);
}
@Override
public void fixUsage() throws IncorrectOperationException {
final PsiElement element = getElement();
if (element instanceof PsiMethodCallExpression) {
PsiReferenceExpression methodExpression = ((PsiMethodCallExpression)element).getMethodExpression();
final PsiMethod superConstructor = (PsiMethod)methodExpression.resolve();
if (superConstructor != null) {
PsiMethod methodCopy = JavaPsiFacade.getElementFactory(getProject()).createMethod("toInline", PsiType.VOID);
final PsiCodeBlock constructorBody = superConstructor.getBody();
if (constructorBody != null) {
final PsiCodeBlock methodBody = methodCopy.getBody();
assert methodBody != null;
methodBody.replace(constructorBody);
methodCopy.getParameterList().replace(superConstructor.getParameterList());
methodCopy.getThrowsList().replace(superConstructor.getThrowsList());
methodExpression = (PsiReferenceExpression)methodExpression.replace(JavaPsiFacade.getElementFactory(getProject()).createExpressionFromText(methodCopy.getName(), methodExpression));
final PsiClass inliningClass = superConstructor.getContainingClass();
assert inliningClass != null;
methodCopy = (PsiMethod)inliningClass.add(methodCopy);
final InlineMethodProcessor inlineMethodProcessor = new InlineMethodProcessor(getProject(), methodCopy, methodExpression, null, true);
inlineMethodProcessor.run();
methodCopy.delete();
}
}
}
}
@Override
public String getConflictMessage() {
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final PsiElement element = getElement();
if (element instanceof PsiMethodCallExpression) {
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element;
final PsiMethod superConstructor = methodCallExpression.resolveMethod();
if (superConstructor != null) {
InlineMethodProcessor.addInaccessibleMemberConflicts(superConstructor, new UsageInfo[]{new UsageInfo(methodCallExpression.getMethodExpression())}, new ReferencedElementsCollector(){
@Override
protected void checkAddMember(@NotNull PsiMember member) {
if (!PsiTreeUtil.isAncestor(superConstructor.getContainingClass(), member, false)) {
super.checkAddMember(member);
}
}
}, conflicts);
if (InlineMethodProcessor.checkBadReturns(superConstructor) && !InlineUtil.allUsagesAreTailCalls(superConstructor)) {
conflicts.putValue(superConstructor, CommonRefactoringUtil.capitalize(RefactoringBundle.message("refactoring.is.not.supported.when.return.statement.interrupts.the.execution.flow", "") + " of super constructor"));
}
}
}
return conflicts.isEmpty() ? null : conflicts.values().iterator().next(); //todo
}
}

View File

@@ -17,16 +17,22 @@ package com.intellij.refactoring.util;
import com.intellij.codeInsight.ChangeContextUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.RedundantCastUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NonNls;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author ven
@@ -235,4 +241,43 @@ public class InlineUtil {
return false;
}
}
public static boolean allUsagesAreTailCalls(final PsiMethod method) {
final List<PsiReference> nonTailCallUsages = new ArrayList<PsiReference>();
boolean result = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
public void run() {
ReferencesSearch.search(method).forEach(new Processor<PsiReference>() {
public boolean process(final PsiReference psiReference) {
ProgressManager.getInstance().checkCanceled();
if (getTailCallType(psiReference) == TailCallType.None) {
nonTailCallUsages.add(psiReference);
return false;
}
return true;
}
});
}
}, RefactoringBundle.message("inline.method.checking.tail.calls.progress"), true, method.getProject());
return result && nonTailCallUsages.isEmpty();
}
public static TailCallType getTailCallType(final PsiReference psiReference) {
PsiElement element = psiReference.getElement();
PsiExpression methodCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
if (methodCall == null) return TailCallType.None;
if (methodCall.getParent() instanceof PsiReturnStatement) return TailCallType.Return;
if (methodCall.getParent() instanceof PsiExpressionStatement) {
PsiStatement callStatement = (PsiStatement) methodCall.getParent();
PsiMethod callerMethod = PsiTreeUtil.getParentOfType(callStatement, PsiMethod.class);
if (callerMethod != null) {
final PsiStatement[] psiStatements = callerMethod.getBody().getStatements();
return psiStatements.length > 0 && callStatement == psiStatements [psiStatements.length-1] ? TailCallType.Simple : TailCallType.None;
}
}
return TailCallType.None;
}
public enum TailCallType {
None, Simple, Return
}
}

View File

@@ -0,0 +1,7 @@
class Test {
private final String field;
Test(){
field = "text";
}
}

View File

@@ -0,0 +1,8 @@
class Super {
private final String field;
public Super() {
field = "text";
}
}

View File

@@ -0,0 +1,5 @@
class Test extends Super{
Test(){
super();
}
}

View File

@@ -0,0 +1,15 @@
class Test {
String s;
Test(String s){
super(s);
System.out.println("hello");
}
void foo() {
Test s = new Test(null);
s.bar();
}
void bar() {}
}

View File

@@ -0,0 +1,15 @@
class Super {
String s;
Super(String s) {
if (s != null) {
this.s = s;
}
}
void foo() {
Super s = new Super(null);
s.bar();
}
void bar() {}
}

View File

@@ -0,0 +1,6 @@
class Test extends Super{
Test(String s){
super(s);
System.out.println("hello");
}
}

View File

@@ -0,0 +1,13 @@
class Test {
Test(String s){
super(s);
System.out.println("hello");
}
void foo() {
Test s = new Test(null);
s.bar();
}
void bar() {}
}

View File

@@ -0,0 +1,13 @@
class Super {
Super(String s) {
if (s == null) return;
System.out.println("s:" + s);
}
void foo() {
Super s = new Super(null);
s.bar();
}
void bar() {}
}

View File

@@ -0,0 +1,7 @@
class Test extends Super{
Test(String s){
super(s);
System.out.println("hello");
}
}

View File

@@ -135,6 +135,17 @@ public class InlineSuperClassTest extends MultiFileTestCase {
doTest();
}
public void testSuperConstructorWithReturnInside() throws Exception {
doTest(true);
}
public void testSuperConstructorWithFieldInitialization() throws Exception {
doTest();
}
public void testSuperConstructorWithParam() throws Exception {
doTest();
}
public void testMultipleSubclasses() throws Exception {
doTest(new PerformAction() {

View File

@@ -9,6 +9,7 @@ import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.refactoring.MockInlineMethodOptions;
import com.intellij.refactoring.util.InlineUtil;
import com.intellij.testFramework.LightCodeInsightTestCase;
import org.jetbrains.annotations.NonNls;
@@ -151,7 +152,7 @@ public class InlineMethodTest extends LightCodeInsightTestCase {
PsiReferenceExpression refExpr = ref instanceof PsiReferenceExpression ? (PsiReferenceExpression)ref : null;
assertTrue(element instanceof PsiMethod);
PsiMethod method = (PsiMethod)element;
final boolean condition = InlineMethodProcessor.checkBadReturns(method) && !InlineMethodHandler.allUsagesAreTailCalls(method);
final boolean condition = InlineMethodProcessor.checkBadReturns(method) && !InlineUtil.allUsagesAreTailCalls(method);
assertFalse("Bad returns found", condition);
InlineOptions options = new MockInlineMethodOptions();
final InlineMethodProcessor processor = new InlineMethodProcessor(getProject(), method, refExpr, myEditor, options.isInlineThisOnly());