blob: 2ef923a81554d0c85c5eaaefc4edd4fdbb3b6dd5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.groovy.refactoring;
import java.util.Collections;
import java.util.Set;
import javax.swing.text.JTextComponent;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ConstructorNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.PackageNode;
import org.codehaus.groovy.ast.PropertyNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.MethodCallExpression;
import org.codehaus.groovy.ast.expr.VariableExpression;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.csl.api.ElementKind;
import org.netbeans.modules.groovy.editor.api.ASTUtils;
import org.netbeans.modules.groovy.editor.api.AstPath;
import org.netbeans.modules.groovy.editor.api.ElementUtils;
import org.netbeans.modules.groovy.editor.api.FindTypeUtils;
import org.netbeans.modules.groovy.editor.api.parser.GroovyParserResult;
import org.netbeans.modules.groovy.refactoring.findusages.model.ClassRefactoringElement;
import org.netbeans.modules.groovy.refactoring.findusages.model.MethodRefactoringElement;
import org.netbeans.modules.groovy.refactoring.findusages.model.RefactoringElement;
import org.netbeans.modules.groovy.refactoring.findusages.model.VariableRefactoringElement;
import org.netbeans.modules.groovy.refactoring.utils.FindMethodUtils;
import org.netbeans.modules.groovy.refactoring.utils.FindPossibleMethods;
import org.netbeans.modules.groovy.refactoring.utils.GroovyProjectUtil;
import org.netbeans.modules.groovy.refactoring.utils.TypeResolver;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.refactoring.spi.ui.RefactoringUI;
import org.netbeans.modules.refactoring.spi.ui.UI;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.windows.TopComponent;
/**
* Abstract groovy refactoring task. In the current state it is always either
* TextComponentTask (which means refactoring called from the editor) or
* NodeToElementTask (refactoring called on the concrete node)
*
* @author Martin Janicek
*/
public abstract class RefactoringTask extends UserTask implements Runnable {
private RefactoringTask() {
}
public abstract boolean isValid();
protected abstract static class TextComponentTask extends RefactoringTask {
private final FileObject fileObject;
private JTextComponent textC;
private RefactoringUI ui;
protected TextComponentTask(EditorCookie ec, FileObject fileObject) {
this.textC = ec.getOpenedPanes()[0];
this.fileObject = fileObject;
assert textC != null;
assert textC.getCaretPosition() != -1;
assert textC.getSelectionStart() != -1;
assert textC.getSelectionEnd() != -1;
}
@Override
public boolean isValid() {
try {
ParserManager.parse(Collections.singleton(Source.create(fileObject)), this);
return true;
} catch (Exception ex) {
return false;
} catch (AssertionError error) {
return false;
}
}
@Override
public void run(ResultIterator resultIterator) throws Exception {
final GroovyParserResult parserResult = ASTUtils.getParseResult(resultIterator.getParserResult());
final ASTNode root = ASTUtils.getRoot(parserResult);
if (root == null) {
throw new IllegalStateException("Not possible to get correct AST!"); // NOI18N
}
final int caret = textC.getCaretPosition();
final int start = textC.getSelectionStart();
final int end = textC.getSelectionEnd();
final BaseDocument doc = GroovyProjectUtil.getDocument(parserResult, fileObject);
final AstPath path = new AstPath(root, caret, doc);
final ASTNode findingNode = FindTypeUtils.findCurrentNode(path, doc, caret);
final ElementKind kind;
if (findingNode instanceof PackageNode) {
kind = ElementKind.PACKAGE;
} else {
kind = ElementUtils.getKind(path, doc, caret);
}
final RefactoringElement element = createRefactoringElement(path, findingNode, kind);
if (element != null && element.getName() != null && element.getFileObject() != null) {
ui = createRefactoringUI(element, start, end, parserResult);
} else {
throw new IllegalStateException("RefactoringElement isn't initiated correctly!"); // NOI18N
}
}
@SuppressWarnings("fallthrough")
private RefactoringElement createRefactoringElement(AstPath path, ASTNode currentNode, ElementKind kind) {
switch (kind) {
case CLASS:
case INTERFACE:
if (currentNode instanceof VariableExpression) {
return new ClassRefactoringElement(fileObject, TypeResolver.resolveType(path, fileObject));
}
return new ClassRefactoringElement(fileObject, currentNode);
case METHOD:
case CONSTRUCTOR:
final ASTNode leaf = path.leaf();
final ASTNode leafParent = path.leafParent();
if (leaf instanceof MethodNode || leaf instanceof ConstructorNode) {
return new MethodRefactoringElement(fileObject, leaf, ElementUtils.getDeclaringClass(leaf));
}
if (leaf instanceof ConstructorCallExpression) {
final ConstructorCallExpression constructorCall = (ConstructorCallExpression) leaf;
return new ClassRefactoringElement(fileObject, constructorCall.getType());
}
if (leaf instanceof ConstantExpression && leafParent instanceof MethodCallExpression) {
final MethodNode methodNode = FindMethodUtils.findMethod(path, (MethodCallExpression) leafParent);
final ClassNode methodType = FindMethodUtils.findMethodType(path, (MethodCallExpression) leafParent);
final String methodName = ((ConstantExpression) leaf).getText();
if (methodType != null) {
if (methodNode != null) {
// This can happen in situations:
// * method()
// * this.method()
// * new SomeClassName().method()
return new MethodRefactoringElement(fileObject, methodNode, methodType);
} else {
// This can happen in situation:
// * SomeClassName abc = new SomeClassName()
// * abc.method()
final Set<MethodNode> possibleMethods = FindPossibleMethods.findPossibleMethods(fileObject, methodType.getName(), methodName);
if (possibleMethods.size() > 0) {
return new MethodRefactoringElement(fileObject, possibleMethods.iterator().next(), methodType);
}
}
} else {
// This can happen in situation with dynamic type:
// * def abc = new SomeClassName()
// * abc.method()
return null;
// We have to improve type interference for dynamic types --> see issue 219905
// return new MethodRefactoringElement(fileObject, leafParent);
}
}
assert false; // Should never happened!
case VARIABLE:
final ClassNode variableType = TypeResolver.resolveType(path, fileObject);
return new VariableRefactoringElement(fileObject, variableType, currentNode.getText());
case PROPERTY:
case FIELD:
if (currentNode instanceof ClassNode) {
return new ClassRefactoringElement(fileObject, currentNode);
} else if (currentNode instanceof FieldNode) {
final FieldNode field = (FieldNode) currentNode;
return new VariableRefactoringElement(fileObject, field.getOwner(), field.getName());
} else if (currentNode instanceof PropertyNode) {
final FieldNode field = ((PropertyNode) currentNode).getField();
return new VariableRefactoringElement(fileObject, field.getOwner(), field.getName());
}
case PACKAGE:
// return new PackageRefactoringElement(fileObject, currentNode);
default:
throw new IllegalStateException("Unknown element kind. Refactoring shouldn't be enabled in this context !"); // NOI18N
}
}
@Override
public final void run() {
try {
ParserManager.parse(Collections.singleton(Source.create(fileObject)), this);
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
UI.openRefactoringUI(ui, TopComponent.getRegistry().getActivated());
}
protected abstract RefactoringUI createRefactoringUI(RefactoringElement selectedElement, int startOffset, int endOffset, GroovyParserResult info);
}
protected abstract static class NodeToElementTask extends RefactoringTask {
private final FileObject fileObject;
private RefactoringUI ui;
protected NodeToElementTask(FileObject fileObject) {
this.fileObject = fileObject;
}
@Override
public boolean isValid() {
try {
ParserManager.parse(Collections.singleton(Source.create(fileObject)), this);
} catch (Exception ex) {
return false;
}
return true;
}
@Override
public void run(ResultIterator resultIterator) throws Exception {
final GroovyParserResult parserResult = ASTUtils.getParseResult(resultIterator.getParserResult());
final ASTNode root = ASTUtils.getRoot(parserResult);
if (root == null) {
return;
}
final RefactoringElement element = new ClassRefactoringElement(fileObject, root);
if (element.getName() != null) {
ui = createRefactoringUI(element, parserResult);
}
if (ui == null) {
throw new IllegalStateException();
}
}
@Override
public final void run() {
try {
ParserManager.parse(Collections.singleton(Source.create(fileObject)), this);
} catch (Exception ex) {
return;
}
UI.openRefactoringUI(ui);
}
protected abstract RefactoringUI createRefactoringUI(RefactoringElement selectedElement, GroovyParserResult info);
}
}