convert static method to instance method of the same class without corresponding parameter (IDEA-64834)

This commit is contained in:
Anna.Kozlova
2017-04-12 14:09:10 +02:00
parent 3f7a67cdfc
commit d9c6dfc42c
12 changed files with 228 additions and 92 deletions
@@ -17,9 +17,8 @@ package com.intellij.refactoring.convertToInstanceMethod;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.help.HelpManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiVariable;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.move.moveInstanceMethod.MoveInstanceMethodDialogBase;
@@ -35,16 +34,16 @@ import java.awt.event.MouseEvent;
*/
public class ConvertToInstanceMethodDialog extends MoveInstanceMethodDialogBase {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.convertToInstanceMethod.ConvertToInstanceMethodDialog");
public ConvertToInstanceMethodDialog(final PsiMethod method, final PsiParameter[] variables) {
public ConvertToInstanceMethodDialog(final PsiMethod method, final PsiElement[] variables) {
super(method, variables, ConvertToInstanceMethodHandler.REFACTORING_NAME);
init();
}
protected void doAction() {
final PsiVariable targetVariable = (PsiVariable)myList.getSelectedValue();
LOG.assertTrue(targetVariable instanceof PsiParameter, targetVariable);
final PsiElement targetVariable = (PsiElement)myList.getSelectedValue();
LOG.assertTrue(targetVariable != null);
final ConvertToInstanceMethodProcessor processor = new ConvertToInstanceMethodProcessor(myMethod.getProject(),
myMethod, (PsiParameter)targetVariable,
myMethod, targetVariable,
myVisibilityPanel.getVisibility());
if (!verifyTargetClass(processor.getTargetClass())) return;
invokeRefactoring(processor);
@@ -17,8 +17,6 @@ package com.intellij.refactoring.convertToInstanceMethod;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
@@ -31,6 +29,7 @@ import com.intellij.refactoring.util.CommonRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -71,44 +70,30 @@ public class ConvertToInstanceMethodHandler implements RefactoringActionHandler
return;
}
final PsiParameter[] parameters = method.getParameterList().getParameters();
List<PsiParameter> suitableParameters = new ArrayList<>();
boolean classTypesFound = false;
boolean resolvableClassesFound = false;
boolean classesInProjectFound = false;
List<PsiElement> targetQualifiers = new ArrayList<>();
for (final PsiParameter parameter : parameters) {
final PsiType type = parameter.getType();
if (type instanceof PsiClassType) {
classTypesFound = true;
final PsiClass psiClass = ((PsiClassType)type).resolve();
if (psiClass != null && !(psiClass instanceof PsiTypeParameter)) {
resolvableClassesFound = true;
final boolean inProject = method.getManager().isInProject(psiClass);
if (inProject) {
classesInProjectFound = true;
suitableParameters.add(parameter);
if (method.getManager().isInProject(psiClass)) {
targetQualifiers.add(parameter);
}
}
}
}
if (suitableParameters.isEmpty()) {
String message = null;
if (!classTypesFound) {
message = RefactoringBundle.message("convertToInstanceMethod.no.parameters.with.reference.type");
}
else if (!resolvableClassesFound) {
message = RefactoringBundle.message("convertToInstanceMethod.all.reference.type.parametres.have.unknown.types");
}
else if (!classesInProjectFound) {
message = RefactoringBundle.message("convertToInstanceMethod.all.reference.type.parameters.are.not.in.project");
}
LOG.assertTrue(message != null);
Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(message), REFACTORING_NAME, HelpID.CONVERT_TO_INSTANCE_METHOD);
return;
PsiClass containingClass = method.getContainingClass();
if (containingClass == null || containingClass.getQualifiedName() == null) return;
String className = containingClass.getName();
PsiMethod[] constructors = containingClass.getConstructors();
boolean noArgConstructor =
constructors.length == 0 || Arrays.stream(constructors).anyMatch(constructor -> constructor.getParameterList().getParametersCount() == 0);
if (noArgConstructor) {
targetQualifiers.add(JavaPsiFacade.getElementFactory(project).createExpressionFromText("new " + className + "()", method));
}
new ConvertToInstanceMethodDialog(
method,
suitableParameters.toArray(new PsiParameter[suitableParameters.size()])).show();
targetQualifiers.toArray(new PsiElement[targetQualifiers.size()])).show();
}
}
@@ -29,6 +29,7 @@ import com.intellij.psi.javadoc.PsiDocTagValue;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
@@ -54,7 +55,7 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
private static final Logger LOG =
Logger.getInstance("#com.intellij.refactoring.convertToInstanceMethod.ConvertToInstanceMethodProcessor");
private PsiMethod myMethod;
private PsiParameter myTargetParameter;
private @Nullable PsiParameter myTargetParameter;
private PsiClass myTargetClass;
private Map<PsiTypeParameter, PsiTypeParameter> myTypeParameterReplacements;
private static final Key<PsiTypeParameter> BIND_TO_TYPE_PARAMETER = Key.create("REPLACEMENT");
@@ -64,17 +65,22 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
public ConvertToInstanceMethodProcessor(final Project project,
final PsiMethod method,
final PsiParameter targetParameter,
final PsiElement targetParameter,
final String newVisibility) {
super(project);
myMethod = method;
myTargetParameter = targetParameter;
myTargetParameter = targetParameter instanceof PsiParameter ? (PsiParameter)targetParameter : null;
LOG.assertTrue(method.hasModifierProperty(PsiModifier.STATIC));
LOG.assertTrue(myTargetParameter.getDeclarationScope() == myMethod);
LOG.assertTrue(myTargetParameter.getType() instanceof PsiClassType);
final PsiType type = myTargetParameter.getType();
LOG.assertTrue(type instanceof PsiClassType);
myTargetClass = ((PsiClassType)type).resolve();
if (myTargetParameter != null) {
LOG.assertTrue(myTargetParameter.getDeclarationScope() == myMethod);
LOG.assertTrue(myTargetParameter.getType() instanceof PsiClassType);
final PsiType type = myTargetParameter.getType();
LOG.assertTrue(type instanceof PsiClassType);
myTargetClass = ((PsiClassType)type).resolve();
}
else {
myTargetClass = method.getContainingClass();
}
myOldVisibility = VisibilityUtil.getVisibilityModifier(method.getModifierList());
myNewVisibility = newVisibility;
}
@@ -89,15 +95,15 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
}
protected void refreshElements(@NotNull PsiElement[] elements) {
LOG.assertTrue(elements.length == 3);
LOG.assertTrue(elements.length > 1);
myMethod = (PsiMethod)elements[0];
myTargetParameter = (PsiParameter)elements[1];
myTargetClass = (PsiClass)elements[2];
myTargetParameter = elements.length == 3 ? (PsiParameter)elements[1] : null;
myTargetClass = (PsiClass)elements[elements.length - 1];
}
@NotNull
protected UsageInfo[] findUsages() {
LOG.assertTrue(myTargetParameter.getDeclarationScope() == myMethod);
LOG.assertTrue(myTargetParameter == null || myTargetParameter.getDeclarationScope() == myMethod);
final Project project = myMethod.getProject();
final PsiReference[] methodReferences =
@@ -111,7 +117,7 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
result.add(new MethodCallUsageInfo((PsiMethodCallExpression)parent));
}
else if (element instanceof PsiMethodReferenceExpression) {
result.add(new MethodReferenceUsageInfo((PsiMethodReferenceExpression)element, myMethod.getParameterList().getParameterIndex(myTargetParameter) == 0));
result.add(new MethodReferenceUsageInfo((PsiMethodReferenceExpression)element, myTargetParameter == null || myMethod.getParameterList().getParameterIndex(myTargetParameter) == 0));
}
}
else if (element instanceof PsiDocTagValue) {
@@ -119,10 +125,12 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
}
}
for (final PsiReference ref : ReferencesSearch.search(myTargetParameter, new LocalSearchScope(myMethod), false)) {
final PsiElement element = ref.getElement();
if (element instanceof PsiReferenceExpression || element instanceof PsiDocParamRef) {
result.add(new ParameterUsageInfo(ref));
if (myTargetParameter != null) {
for (final PsiReference ref : ReferencesSearch.search(myTargetParameter, new LocalSearchScope(myMethod), false)) {
final PsiElement element = ref.getElement();
if (element instanceof PsiReferenceExpression || element instanceof PsiDocParamRef) {
result.add(new ParameterUsageInfo(ref));
}
}
}
@@ -177,23 +185,33 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
}
for (final UsageInfo usageInfo : usagesIn) {
PsiElement place = null;
if (usageInfo instanceof MethodCallUsageInfo) {
final PsiMethodCallExpression methodCall = ((MethodCallUsageInfo)usageInfo).getMethodCall();
final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions();
final int index = myMethod.getParameterList().getParameterIndex(myTargetParameter);
if (index < expressions.length) {
PsiExpression instanceValue = expressions[index];
instanceValue = RefactoringUtil.unparenthesizeExpression(instanceValue);
if (instanceValue instanceof PsiLiteralExpression && ((PsiLiteralExpression)instanceValue).getValue() == null) {
String message = RefactoringBundle.message("0.contains.call.with.null.argument.for.parameter.1",
RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(methodCall), true),
CommonRefactoringUtil.htmlEmphasize(myTargetParameter.getName()));
conflicts.putValue(methodCall, message);
place = ((MethodCallUsageInfo)usageInfo).getMethodCall();
if (myTargetParameter != null) {
final PsiExpression[] expressions = ((PsiMethodCallExpression)place).getArgumentList().getExpressions();
final int index = myMethod.getParameterList().getParameterIndex(myTargetParameter);
if (index < expressions.length) {
PsiExpression instanceValue = expressions[index];
instanceValue = RefactoringUtil.unparenthesizeExpression(instanceValue);
if (instanceValue instanceof PsiLiteralExpression && ((PsiLiteralExpression)instanceValue).getValue() == null) {
String message = RefactoringBundle.message("0.contains.call.with.null.argument.for.parameter.1",
RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(place), true),
CommonRefactoringUtil.htmlEmphasize(myTargetParameter.getName()));
conflicts.putValue(place, message);
}
}
}
}
else if (usageInfo instanceof MethodReferenceUsageInfo && !((MethodReferenceUsageInfo)usageInfo).isApplicableBySecondSearch()) {
conflicts.putValue(((MethodReferenceUsageInfo)usageInfo).getExpression(), RefactoringBundle.message("expand.method.reference.warning"));
else if (usageInfo instanceof MethodReferenceUsageInfo) {
place = ((MethodReferenceUsageInfo)usageInfo).getExpression();
if (!((MethodReferenceUsageInfo)usageInfo).isApplicableBySecondSearch()) {
conflicts.putValue(place, RefactoringBundle.message("expand.method.reference.warning"));
}
}
if (myTargetParameter == null && place != null && myTargetClass.hasTypeParameters() && !thisAccessExpressionApplicable(place)) {
conflicts.putValue(place, "Impossible to infer class type arguments. When proceed, raw " + myTargetClass.getName() + " would be created");
}
}
@@ -237,7 +255,7 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
}
prepareTypeParameterReplacement();
myTargetParameter.delete();
if (myTargetParameter != null) myTargetParameter.delete();
ChangeContextUtil.encodeContextInfo(myMethod, true);
if (!myTargetClass.isInterface()) {
PsiMethod method = addMethodToClass(myTargetClass);
@@ -271,7 +289,18 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
if (usage.isApplicableBySecondSearch()) {
PsiExpression qualifierExpression = expression.getQualifierExpression();
LOG.assertTrue(qualifierExpression != null);
qualifierExpression.replace(JavaPsiFacade.getElementFactory(myProject).createReferenceExpression(myTargetClass));
PsiElementFactory factory = JavaPsiFacade.getElementFactory(myProject);
PsiElement qualifier;
if (myTargetParameter != null) {
qualifier = factory.createReferenceExpression(myTargetClass);
}
else {
boolean thisAccess = thisAccessExpressionApplicable(expression);
qualifier = thisAccess
? factory.createExpressionFromText("this", qualifierExpression)
: createSyntheticAccessExpression(factory, expression);
}
qualifierExpression.replace(qualifier);
}
else {
PsiLambdaExpression lambdaExpression = LambdaRefactoringUtil.convertMethodReferenceToLambda(expression, false, true);
@@ -403,12 +432,28 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
}
private void processMethodCall(final PsiMethodCallExpression methodCall) throws IncorrectOperationException {
PsiParameterList parameterList = myMethod.getParameterList();
PsiElementFactory factory = JavaPsiFacade.getInstance(myMethod.getProject()).getElementFactory();
int parameterIndex = parameterList.getParameterIndex(myTargetParameter);
PsiExpression[] arguments = methodCall.getArgumentList().getExpressions();
if (arguments.length <= parameterIndex) return;
final PsiReferenceExpression methodExpression = methodCall.getMethodExpression();
PsiExpression argument;
if (myTargetParameter != null) {
PsiParameterList parameterList = myMethod.getParameterList();
int parameterIndex = parameterList.getParameterIndex(myTargetParameter);
PsiExpression[] arguments = methodCall.getArgumentList().getExpressions();
if (arguments.length <= parameterIndex) return;
argument = arguments[parameterIndex];
}
else {
if (thisAccessExpressionApplicable(methodCall)) {
PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
if (qualifierExpression != null) {
qualifierExpression.delete();
}
return;
}
argument = createSyntheticAccessExpression(factory, methodCall);
}
final PsiExpression qualifier;
if (methodExpression.getQualifierExpression() != null) {
qualifier = methodExpression.getQualifierExpression();
@@ -417,16 +462,29 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
final PsiReferenceExpression newRefExpr = (PsiReferenceExpression)factory.createExpressionFromText("x." + myMethod.getName(), null);
qualifier = ((PsiReferenceExpression)methodExpression.replace(newRefExpr)).getQualifierExpression();
}
qualifier.replace(arguments[parameterIndex]);
arguments[parameterIndex].delete();
qualifier.replace(argument);
argument.delete();
}
private PsiExpression createSyntheticAccessExpression(PsiElementFactory factory, PsiElement context) {
return factory.createExpressionFromText("new " + myTargetClass.getName() + "()", context);
}
private boolean thisAccessExpressionApplicable(PsiElement expression) {
return PsiTreeUtil.isAncestor(myTargetClass, expression, false) && PsiUtil.getEnclosingStaticElement(expression, myTargetClass) == null;
}
@NotNull
protected String getCommandName() {
return ConvertToInstanceMethodHandler.REFACTORING_NAME;
}
@Nullable
public Map<PsiTypeParameter, PsiTypeParameter> buildTypeParameterReplacements() {
if (myTargetParameter == null) {
return Collections.emptyMap();
}
final PsiClassType type = (PsiClassType)myTargetParameter.getType();
final PsiSubstitutor substitutor = type.resolveGenerics().getSubstitutor();
return calculateReplacementMap(substitutor, myTargetClass, myMethod);
@@ -454,6 +512,7 @@ public class ConvertToInstanceMethodProcessor extends BaseRefactoringProcessor {
return myMethod;
}
@Nullable
public PsiParameter getTargetParameter() {
return myTargetParameter;
}
@@ -106,7 +106,7 @@ public class MoveInstanceMethodDialog extends MoveInstanceMethodDialogBase {
textField.setEnabled(true);
}
final PsiVariable variable = myVariables[selectedIndex];
final PsiElement variable = myVariables[selectedIndex];
if (variable instanceof PsiField) {
final PsiField field = (PsiField)variable;
final PsiClass hisClass = field.getContainingClass();
@@ -18,10 +18,7 @@ package com.intellij.refactoring.move.moveInstanceMethod;
import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.*;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiUtil;
@@ -42,7 +39,7 @@ import java.awt.*;
*/
public abstract class MoveInstanceMethodDialogBase extends MoveDialogBase {
protected final PsiMethod myMethod;
protected final PsiVariable[] myVariables;
protected final PsiElement[] myVariables;
public JComponent getPreferredFocusedComponent() {
return myList;
@@ -52,7 +49,7 @@ public abstract class MoveInstanceMethodDialogBase extends MoveDialogBase {
protected JavaVisibilityPanel myVisibilityPanel;
protected final String myRefactoringName;
public MoveInstanceMethodDialogBase(PsiMethod method, PsiVariable[] variables, String refactoringName) {
public MoveInstanceMethodDialogBase(PsiMethod method, PsiElement[] variables, String refactoringName) {
super(method.getProject(), true);
myMethod = method;
myVariables = variables;
@@ -142,12 +139,17 @@ public abstract class MoveInstanceMethodDialogBase extends MoveDialogBase {
private static class MyListCellRenderer extends DefaultListCellRenderer {
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
final PsiVariable psiVariable = (PsiVariable)value;
final String text = PsiFormatUtil.formatVariable(psiVariable,
PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_TYPE,
PsiSubstitutor.EMPTY);
setIcon(psiVariable.getIcon(0));
setText(text);
if (value instanceof PsiVariable) {
final PsiVariable psiVariable = (PsiVariable)value;
final String text = PsiFormatUtil.formatVariable(psiVariable,
PsiFormatUtil.SHOW_NAME | PsiFormatUtil.SHOW_TYPE,
PsiSubstitutor.EMPTY);
setIcon(psiVariable.getIcon(0));
setText(text);
}
else if (value instanceof PsiExpression) {
setText(((PsiExpression)value).getText());
}
return this;
}
}
@@ -31,10 +31,9 @@ public class MoveInstanceMethodViewDescriptor extends UsageViewDescriptorAdapter
private final PsiVariable myTargetVariable;
private final PsiClass myTargetClass;
public MoveInstanceMethodViewDescriptor(
PsiMethod method,
PsiVariable targetVariable,
PsiClass targetClass) {
public MoveInstanceMethodViewDescriptor(PsiMethod method,
PsiVariable targetVariable,
PsiClass targetClass) {
super();
myMethod = method;
myTargetVariable = targetVariable;
@@ -43,7 +42,7 @@ public class MoveInstanceMethodViewDescriptor extends UsageViewDescriptorAdapter
@NotNull
public PsiElement[] getElements() {
return new PsiElement[] {myMethod, myTargetVariable, myTargetClass};
return myTargetVariable == null ? new PsiElement[] {myTargetClass} : new PsiElement[] {myMethod, myTargetVariable, myTargetClass};
}
public String getProcessedElementsHeader() {
@@ -0,0 +1,26 @@
class Bar {
static void f<caret>oo() {
}
void m(){
Bar.foo();
}
{
Runnable r = Bar::foo;
}
static {
Runnable r = Bar::foo;
}
}
class Bar1 {
void m() {
Bar.foo();
}
{
Runnable r = Bar::foo;
}
}
@@ -0,0 +1,26 @@
class Bar {
void foo() {
}
void m(){
foo();
}
{
Runnable r = this::foo;
}
static {
Runnable r = new Bar()::foo;
}
}
class Bar1 {
void m() {
new Bar().foo();
}
{
Runnable r = new Bar()::foo;
}
}
@@ -0,0 +1,26 @@
class Bar<T> {
static void f<caret>oo() {
}
void m(){
Bar.foo();
}
{
Runnable r = Bar::foo;
}
static {
Runnable r = Bar::foo;
}
}
class Bar1 {
void m() {
Bar.foo();
}
{
Runnable r = Bar::foo;
}
}
@@ -15,6 +15,7 @@
*/
package com.intellij.refactoring.convertToInstanceMethod;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.refactoring.BaseRefactoringProcessor;
@@ -32,6 +33,19 @@ public class ConvertToInstance8MethodTest extends ConvertToInstanceMethodTest {
doTest(0);
}
public void testConvertToInstanceMethodOfTheSameClass() throws Exception {
doTest(-1);
}
public void testConvertToInstanceMethodOfTheSameClassWithTypeParams() throws Exception {
try {
doTest(-1);
}
catch (BaseRefactoringProcessor.ConflictsInTestsException e) {
assertEquals(StringUtil.trimEnd(StringUtil.repeat("Impossible to infer class type arguments. When proceed, raw Bar would be created\n", 3), "\n"), e.getMessage());
}
}
public void testMethodReferenceToLambda() throws Exception {
BaseRefactoringProcessor.ConflictsInTestsException.withIgnoredConflicts(() -> doTest(1));
}
@@ -62,7 +62,7 @@ public class ConvertToInstanceMethodTest extends LightRefactoringTestCase {
assertTrue("<caret> is not on method name", targetElement instanceof PsiMethod);
PsiMethod method = (PsiMethod) targetElement;
new ConvertToInstanceMethodProcessor(getProject(),
method, method.getParameterList().getParameters()[targetParameter],
method, targetParameter < 0 ? null : method.getParameterList().getParameters()[targetParameter],
visibility).run();
checkResultByFile(filePath + ".after");
@@ -76,7 +76,7 @@ visibility.package.local=Pac&kage local
visibility.border.title=Visibility
default.visibility.border.title=Default Visibility
visibility.combo.title=&Visibility:
moveInstanceMethod.select.an.instance.parameter=Select an &instance parameter:
moveInstanceMethod.select.an.instance.parameter=Select an &instance expression:
destination.package=Destination &package:
choose.destination.package=Choose Destination Package
interface.name.prompt=&Interface name: