blob: be5c52fc4a7cb50fcc7a1df300e5be9606dd17db [file] [log] [blame]
/*
* Copyright 2000-2014 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 org.jetbrains.plugins.groovy.refactoring.extract.method;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.ui.ConflictsDialog;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
import org.jetbrains.plugins.groovy.refactoring.GrRefactoringError;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
import org.jetbrains.plugins.groovy.refactoring.extract.ExtractUtil;
import org.jetbrains.plugins.groovy.refactoring.extract.GroovyExtractChooser;
import org.jetbrains.plugins.groovy.refactoring.extract.InitialInfo;
import org.jetbrains.plugins.groovy.refactoring.extract.ParameterInfo;
import org.jetbrains.plugins.groovy.refactoring.introduce.GrIntroduceHandlerBase;
import java.util.ArrayList;
import java.util.List;
/**
* @author ilyas
*/
public class GroovyExtractMethodHandler implements RefactoringActionHandler {
protected static String REFACTORING_NAME = GroovyRefactoringBundle.message("extract.method.title");
private static final Logger LOG = Logger.getInstance(GroovyExtractMethodHandler.class);
@Override
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, @Nullable DataContext dataContext) {
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
final SelectionModel model = editor.getSelectionModel();
if (model.hasSelection()) {
invokeImpl(project, editor, file, model.getSelectionStart(), model.getSelectionEnd());
}
else {
final List<GrExpression> expressions = GrIntroduceHandlerBase.collectExpressions(file, editor, editor.getCaretModel().getOffset(), true);
final Pass<GrExpression> callback = new Callback(project, editor, file);
if (expressions.size() == 1) {
callback.pass(expressions.get(0));
}
else if (expressions.isEmpty()) {
model.selectLineAtCaret();
invokeImpl(project, editor, file, model.getSelectionStart(), model.getSelectionEnd());
}
else {
IntroduceTargetChooser.showChooser(editor, expressions, callback, GrIntroduceHandlerBase.GR_EXPRESSION_RENDERER);
}
}
}
private class Callback extends Pass<GrExpression> {
private final Project project;
private final Editor editor;
private final PsiFile file;
private Callback(Project project, Editor editor, PsiFile file) {
this.project = project;
this.editor = editor;
this.file = file;
}
@Override
public void pass(@NotNull final GrExpression selectedValue) {
final TextRange range = selectedValue.getTextRange();
invokeImpl(project, editor, file, range.getStartOffset(), range.getEndOffset());
}
}
private void invokeImpl(Project project, Editor editor, PsiFile file, final int startOffset, final int endOffset) {
try {
final InitialInfo initialInfo = GroovyExtractChooser.invoke(project, editor, file, startOffset, endOffset, true);
if (findConflicts(initialInfo)) return;
performRefactoring(initialInfo, editor);
}
catch (GrRefactoringError e) {
CommonRefactoringUtil.showErrorHint(project, editor, e.getMessage(), REFACTORING_NAME, HelpID.EXTRACT_METHOD);
}
}
private static boolean findConflicts(InitialInfo info) {
//new ConflictsDialog()
final MultiMap<PsiElement, String> conflicts = new MultiMap<PsiElement, String>();
final PsiElement declarationOwner = info.getContext().getParent();
GroovyRecursiveElementVisitor visitor = new GroovyRecursiveElementVisitor() {
@Override
public void visitReferenceExpression(GrReferenceExpression referenceExpression) {
super.visitReferenceExpression(referenceExpression);
GroovyResolveResult resolveResult = referenceExpression.advancedResolve();
PsiElement resolveContext = resolveResult.getCurrentFileResolveContext();
if (resolveContext != null &&
!(resolveContext instanceof GrImportStatement) &&
!PsiTreeUtil.isAncestor(declarationOwner, resolveContext, true) && !skipResult(resolveResult)) {
conflicts.putValue(referenceExpression, GroovyRefactoringBundle
.message("ref.0.will.not.be.resolved.outside.of.current.context", referenceExpression.getText()));
}
}
//skip 'print' and 'println'
private boolean skipResult(GroovyResolveResult result) {
PsiElement element = result.getElement();
if (element instanceof PsiMethod) {
String name = ((PsiMethod)element).getName();
if (!name.startsWith("print")) return false;
if (element instanceof GrGdkMethod) element = ((GrGdkMethod)element).getStaticMethod();
PsiClass aClass = ((PsiMethod)element).getContainingClass();
if (aClass != null) {
String qname = aClass.getQualifiedName();
return GroovyCommonClassNames.DEFAULT_GROOVY_METHODS.equals(qname);
}
}
return false;
}
};
GrStatement[] statements = info.getStatements();
for (GrStatement statement : statements) {
statement.accept(visitor);
}
if (conflicts.isEmpty()) return false;
if (ApplicationManager.getApplication().isUnitTestMode()) {
throw new BaseRefactoringProcessor.ConflictsInTestsException(conflicts.values());
}
ConflictsDialog dialog = new ConflictsDialog(info.getProject(), conflicts);
dialog.show();
return !dialog.isOK();
}
private void performRefactoring(@NotNull final InitialInfo initialInfo, @Nullable final Editor editor) {
final PsiClass owner = PsiUtil.getContextClass(initialInfo.getContext());
LOG.assertTrue(owner!=null);
final ExtractMethodInfoHelper helper = getSettings(initialInfo, owner);
if (helper == null) return;
CommandProcessor.getInstance().executeCommand(helper.getProject(), new Runnable() {
@Override
public void run() {
final AccessToken lock = ApplicationManager.getApplication().acquireWriteActionLock(GroovyExtractMethodHandler.class);
try {
createMethod(helper, owner);
GrStatementOwner declarationOwner =
helper.getStringPartInfo() == null ? GroovyRefactoringUtil.getDeclarationOwner(helper.getStatements()[0]) : null;
GrStatement realStatement = ExtractUtil.replaceStatement(declarationOwner, helper);
// move to offset
if (editor != null) {
PsiDocumentManager.getInstance(helper.getProject()).commitDocument(editor.getDocument());
editor.getSelectionModel().removeSelection();
editor.getCaretModel().moveToOffset(ExtractUtil.getCaretOffset(realStatement));
}
}
finally {
lock.finish();
}
}
}, REFACTORING_NAME, null);
}
private static void createMethod(ExtractMethodInfoHelper helper, PsiClass owner) {
final GrMethod method = ExtractUtil.createMethod(helper);
PsiElement anchor = calculateAnchorToInsertBefore(owner, helper.getContext());
GrMethod newMethod = (GrMethod)owner.addBefore(method, anchor);
renameParameterOccurrences(newMethod, helper);
JavaCodeStyleManager.getInstance(newMethod.getProject()).shortenClassReferences(newMethod);
PsiElement prev = newMethod.getPrevSibling();
if (!PsiUtil.isLineFeed(prev)) {
newMethod.getParent().getNode().addLeaf(GroovyTokenTypes.mNLS, "\n", newMethod.getNode());
}
}
@Nullable
protected ExtractMethodInfoHelper getSettings(@NotNull InitialInfo initialInfo, PsiClass owner) {
GroovyExtractMethodDialog dialog = new GroovyExtractMethodDialog(initialInfo, owner);
dialog.show();
if (!dialog.isOK()) return null;
return dialog.getHelper();
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
// does nothing
}
@Nullable
private static PsiElement calculateAnchorToInsertBefore(PsiClass owner, PsiElement startElement) {
while (startElement != null && !isEnclosingDefinition(owner, startElement)) {
if (startElement.getParent() instanceof GroovyFile) {
return startElement.getNextSibling();
}
startElement = startElement.getParent();
PsiElement parent = startElement.getParent();
if (parent instanceof GroovyFile && ((GroovyFile) parent).getScriptClass() == owner) {
return startElement.getNextSibling();
}
}
return startElement == null ? null : startElement.getNextSibling();
}
private static boolean isEnclosingDefinition(PsiClass owner, PsiElement startElement) {
if (owner instanceof GrTypeDefinition) {
GrTypeDefinition definition = (GrTypeDefinition) owner;
return startElement.getParent() == definition.getBody();
}
return false;
}
private static void renameParameterOccurrences(GrMethod method, ExtractMethodInfoHelper helper) throws IncorrectOperationException {
GrOpenBlock block = method.getBlock();
if (block == null) return;
GrStatement[] statements = block.getStatements();
final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(helper.getProject());
for (ParameterInfo info : helper.getParameterInfos()) {
final String oldName = info.getOldName();
final String newName = info.getName();
final ArrayList<GrExpression> result = new ArrayList<GrExpression>();
if (!oldName.equals(newName)) {
for (final GrStatement statement : statements) {
statement.accept(new PsiRecursiveElementVisitor() {
@Override
public void visitElement(final PsiElement element) {
super.visitElement(element);
if (element instanceof GrReferenceExpression) {
GrReferenceExpression expr = (GrReferenceExpression) element;
if (!expr.isQualified() && oldName.equals(expr.getReferenceName())) {
result.add(expr);
}
}
}
});
for (GrExpression expr : result) {
expr.replaceWithExpression(factory.createExpressionFromText(newName), false);
}
}
}
}
}
}