diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties index bb9ff1941211..340be06e9ffc 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/GroovyIntentionsBundle.properties @@ -103,3 +103,5 @@ closure.is.accessed.outside.of.groovy=Field {0} is accessed outside of Gr write.access.to.closure.variable=Write access to field {0} field.is.used.in.argument.label=Field {0} is used in argument label method.with.signature.already.exists=Method with signature {0} already exists +field.already.exists=Field {0} already exists +method.is.used.outside.of.groovy=Method is used outside of Groovy\ diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertClosureToMethodIntention.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertClosureToMethodIntention.java index 9853df9e106c..6c7103b31fa2 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertClosureToMethodIntention.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertClosureToMethodIntention.java @@ -108,7 +108,9 @@ public class ConvertClosureToMethodIntention extends Intention { } } final PsiClass containingClass = field.getContainingClass(); - final PsiType type = field.getTypeGroovy(); + final GrExpression initializer = field.getInitializerGroovy(); + LOG.assertTrue(initializer != null); + final PsiType type = initializer.getType(); LOG.assertTrue(type instanceof GrClosureType); final GrClosureSignature signature = ((GrClosureType)type).getSignature(); final List signatures = GrClosureSignatureUtil.generateAllMethodSignaturesByClosureSignature(fieldName, signature); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertMethodToClosureIntention.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertMethodToClosureIntention.java index 7823410e28b3..6bf530129f07 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertMethodToClosureIntention.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/intentions/conversions/ConvertMethodToClosureIntention.java @@ -16,19 +16,34 @@ package org.jetbrains.plugins.groovy.intentions.conversions; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiField; +import com.intellij.psi.PsiReference; +import com.intellij.psi.search.searches.MethodReferencesSearch; +import com.intellij.refactoring.ui.ConflictsDialog; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.HashSet; +import com.intellij.util.containers.MultiMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.GroovyFileType; +import org.jetbrains.plugins.groovy.intentions.GroovyIntentionsBundle; import org.jetbrains.plugins.groovy.intentions.base.Intention; import org.jetbrains.plugins.groovy.intentions.base.PsiElementPredicate; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier; import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; +import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinitionBody; import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; +import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; + +import java.util.Collection; /** @@ -43,21 +58,72 @@ public class ConvertMethodToClosureIntention extends Intention { @Override protected void processIntention(@NotNull PsiElement element, Project project, Editor editor) throws IncorrectOperationException { - element = element.getParent(); - final GrMethod method = (GrMethod)element; - StringBuilder builder = new StringBuilder(method.getTextLength()); - String modifiers = method.getModifierList().getText(); - if (modifiers.trim().length() == 0) { - modifiers = GrModifier.DEF; + MultiMap conflicts = new MultiMap(); + final GrMethod method = (GrMethod)element.getParent(); + + final PsiClass containingClass = method.getContainingClass(); + final String methodName = method.getName(); + final PsiField field = containingClass.findFieldByName(methodName, true); + + if (field != null) { + conflicts.putValue(field, GroovyIntentionsBundle.message("field.already.exists", methodName)); } - builder.append(modifiers).append(' '); - builder.append(method.getName()).append("={"); - builder.append(method.getParameterList().getText()).append(" ->"); - final GrOpenBlock block = method.getBlock(); - builder.append(block.getText().substring(1)); - final GrVariableDeclaration variableDeclaration = - GroovyPsiElementFactory.getInstance(element.getProject()).createFieldDeclarationFromText(builder.toString()); - method.replace(variableDeclaration); + + final Collection references = MethodReferencesSearch.search(method).findAll(); + final Collection usagesToConvert = new HashSet(references.size()); + for (PsiReference ref : references) { + final PsiElement psiElement = ref.getElement(); + if (!GroovyFileType.GROOVY_LANGUAGE.equals(psiElement.getLanguage())) { + conflicts.putValue(psiElement, GroovyIntentionsBundle.message("method.is.used.outside.of.groovy")); + } else if (!PsiUtil.isMethodUsage(psiElement)) { + if (psiElement instanceof GrReferenceExpression) { + if (((GrReferenceExpression)psiElement).hasMemberPointer()) { + usagesToConvert.add((GrReferenceExpression)psiElement); + } + } + } + } + if (conflicts.size()>0) { + ConflictsDialog conflictsDialog = new ConflictsDialog(project, conflicts, new Runnable() { + @Override + public void run() { + execute(method, usagesToConvert); + } + }); + conflictsDialog.show(); + if (conflictsDialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) return; + } + execute(method, usagesToConvert); + } + + private static void execute(final GrMethod method, final Collection usagesToConvert) { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(method.getProject()); + + StringBuilder builder = new StringBuilder(method.getTextLength()); + String modifiers = method.getModifierList().getText(); + if (modifiers.trim().length() == 0) { + modifiers = GrModifier.DEF; + } + builder.append(modifiers).append(' '); + builder.append(method.getName()).append("={"); + builder.append(method.getParameterList().getText()).append(" ->"); + final GrOpenBlock block = method.getBlock(); + builder.append(block.getText().substring(1)); + final GrVariableDeclaration variableDeclaration = + GroovyPsiElementFactory.getInstance(method.getProject()).createFieldDeclarationFromText(builder.toString()); + method.replace(variableDeclaration); + + for (GrReferenceExpression element : usagesToConvert) { + final PsiElement qualifier = element.getQualifier(); + final StringBuilder text = new StringBuilder(qualifier.getText()); + element.setQualifierExpression(null); + text.append('.').append(element.getText()); + element.replace(factory.createExpressionFromText(text.toString())); + } + } + }); } private static class MyPredicate implements PsiElementPredicate { diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrReferenceExpression.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrReferenceExpression.java index a82661b8a4fc..65c6bb116e96 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrReferenceExpression.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/api/statements/expressions/GrReferenceExpression.java @@ -43,6 +43,8 @@ public interface GrReferenceExpression extends GrExpression, GrReferenceElement, boolean hasAt(); + boolean hasMemberPointer(); + void replaceDotToken(PsiElement newDotToken); //not caching! diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java index 31e39245d10f..b80e88fcf4ba 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/statements/expressions/GrReferenceExpressionImpl.java @@ -733,6 +733,11 @@ public class GrReferenceExpressionImpl extends GrReferenceElementImpl implements return findChildByType(GroovyTokenTypes.mAT) != null; } + @Override + public boolean hasMemberPointer() { + return findChildByType(GroovyTokenTypes.mMEMBER_POINTER) != null; + } + public boolean isReferenceTo(PsiElement element) { return getManager().areElementsEquivalent(element, GroovyTargetElementEvaluator.correctSearchTargets(resolve())); } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/ConvertMethodToClosureTest.java b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/ConvertMethodToClosureTest.java index da8fd998e6e2..2f1df8411203 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/ConvertMethodToClosureTest.java +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/intentions/ConvertMethodToClosureTest.java @@ -51,6 +51,10 @@ public class ConvertMethodToClosureTest extends GrIntentionTestCase { doClosureToMethodTest(); } + public void testMethodToClosureWithMemberPointer() { + doMethodToClosureTest(); + } + private void doClosureToMethodTest() { doTest(GroovyIntentionsBundle.message("convert.closure.to.method.intention.name"), true); } diff --git a/plugins/groovy/testdata/intentions/convertMethodToClosure/MethodToClosureWithMemberPointer.groovy b/plugins/groovy/testdata/intentions/convertMethodToClosure/MethodToClosureWithMemberPointer.groovy new file mode 100644 index 000000000000..670abc75cef4 --- /dev/null +++ b/plugins/groovy/testdata/intentions/convertMethodToClosure/MethodToClosureWithMemberPointer.groovy @@ -0,0 +1,12 @@ +class X { + def foo(def it = null) {print it} + + def bar() { + print this.&foo + } +} + +final X x = new X() +print x.&foo +x.foo(2) + diff --git a/plugins/groovy/testdata/intentions/convertMethodToClosure/MethodToClosureWithMemberPointer_after.groovy b/plugins/groovy/testdata/intentions/convertMethodToClosure/MethodToClosureWithMemberPointer_after.groovy new file mode 100644 index 000000000000..95bf6c71ad25 --- /dev/null +++ b/plugins/groovy/testdata/intentions/convertMethodToClosure/MethodToClosureWithMemberPointer_after.groovy @@ -0,0 +1,12 @@ +class X { + def foo = {def it = null -> print it} + + def bar() { + print this.foo + } +} + +final X x = new X() +print x.foo +x.foo(2) +