blob: 5ce03c7993ed4ab5f8be861f44b231438fe569a2 [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.refactoring.java.plugins;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.tree.*;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTrees;
import com.sun.source.util.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreeScanner;
import com.sun.source.util.Trees;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.lang.model.element.*;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.netbeans.api.java.lexer.JavaTokenId;
import static org.netbeans.api.java.lexer.JavaTokenId.*;
import org.netbeans.api.java.source.DocTreePathHandle;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.ElementUtilities;
import org.netbeans.api.java.source.GeneratorUtilities;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.refactoring.api.RenameRefactoring;
import org.netbeans.modules.refactoring.java.spi.RefactoringVisitor;
/**
*
* @author Jan Becicka
*/
public class RenameTransformer extends RefactoringVisitor {
private final Set<ElementHandle<ExecutableElement>> allMethods;
private final TreePathHandle handle;
private final DocTreePathHandle docHandle;
private final String newName;
private final boolean renameInComments;
private final RenameRefactoring refactoring;
private Iterable<? extends Element> shadowed;
private Map<ImportTree, ImportTree> imports;
private List<ImportTree> newImports;
public RenameTransformer(TreePathHandle handle, DocTreePathHandle docHandle, RenameRefactoring refactoring, Set<ElementHandle<ExecutableElement>> am, boolean renameInComments) {
super(true);
this.handle = handle;
this.docHandle = docHandle;
this.refactoring = refactoring;
this.newName = refactoring.getNewName();
this.allMethods = am;
this.renameInComments = renameInComments;
}
@Override
public Tree scan(Tree tree, Element p) {
if(p == null && handle == null) {
p = docHandle != null? ((DocTrees)workingCopy.getTrees()).getElement(docHandle.resolve(workingCopy))
: handle.resolveElement(workingCopy);
}
return super.scan(tree, p);
}
@Override
public Tree visitCompilationUnit(CompilationUnitTree node, Element p) {
GeneratorUtilities genUtils = GeneratorUtilities.get(workingCopy);
genUtils.importComments(node, node);
if (renameInComments) {
if (p.getKind() == ElementKind.PARAMETER) {
renameParameterInMethodComments(p);
} else {
String originalName = getOldSimpleName(p);
if (originalName!=null) {
TokenSequence<JavaTokenId> ts = workingCopy.getTokenHierarchy().tokenSequence(JavaTokenId.language());
while (ts.moveNext()) {
Token<JavaTokenId> t = ts.token();
if (isComment(t)) {
rewriteAllInComment(t.text().toString(), ts.offset(), originalName);
}
}
}
}
}
imports = new HashMap<>();
newImports = new LinkedList<>();
Tree value = super.visitCompilationUnit(node, p);
if(!imports.isEmpty() || !newImports.isEmpty()) {
CompilationUnitTree newNode = node;
for (Map.Entry<ImportTree, ImportTree> entry : imports.entrySet()) {
newNode = make.removeCompUnitImport(newNode, entry.getKey());
newNode = make.addCompUnitImport(newNode, entry.getValue());
}
for (ImportTree newImport : newImports) {
newNode = make.addCompUnitImport(newNode, newImport);
}
rewrite(node, newNode);
}
return value;
}
@Override
public Tree visitIdentifier(IdentifierTree node, Element p) {
renameUsageIfMatch(getCurrentPath(), node,p);
renameShadowIfMatch(getCurrentPath(), node, p);
return super.visitIdentifier(node, p);
}
@Override
public Tree visitMemberSelect(MemberSelectTree node, Element p) {
renameUsageIfMatch(getCurrentPath(), node,p);
return super.visitMemberSelect(node, p);
}
@Override
public Tree visitMemberReference(MemberReferenceTree node, Element p) {
renameUsageIfMatch(getCurrentPath(), node,p);
return super.visitMemberReference(node, p);
}
@Override
public Tree visitLabeledStatement(LabeledStatementTree tree, Element p) {
if(handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT && tree == handle.resolve(workingCopy).getLeaf()) {
LabeledStatementTree newTree = make.LabeledStatement(newName, tree.getStatement());
rewrite(tree, newTree);
}
return super.visitLabeledStatement(tree, p);
}
@Override
public Tree visitContinue(ContinueTree tree, Element p) {
if(handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT) {
StatementTree target = workingCopy.getTreeUtilities().getBreakContinueTarget(getCurrentPath());
if(target == handle.resolve(workingCopy).getLeaf()) {
ContinueTree newTree = make.Continue(newName);
rewrite(tree, newTree);
}
}
return super.visitContinue(tree, p);
}
@Override
public Tree visitBreak(BreakTree tree, Element p) {
if(handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT) {
Tree target = workingCopy.getTreeUtilities().getBreakContinueTargetTree(getCurrentPath());
if(target == handle.resolve(workingCopy).getLeaf()) {
BreakTree newTree = make.Break(newName);
rewrite(tree, newTree);
}
}
return super.visitBreak(tree, p);
}
private String getOldSimpleName(Element p) {
if (p!=null) {
return p.getSimpleName().toString();
}
for (ElementHandle<ExecutableElement> mh : allMethods) {
ExecutableElement baseMethod = mh.resolve(workingCopy);
if (baseMethod == null) {
continue;
}
return baseMethod.getSimpleName().toString();
}
return null;
}
private void renameUsageIfMatch(final TreePath path, Tree tree, Element elementToFind) {
if (JavaPluginUtils.isSyntheticPath(workingCopy, path) || (handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT)) {
return;
}
TreePath elementPath = path;
Trees trees = workingCopy.getTrees();
Element el = workingCopy.getTrees().getElement(elementPath);
if (el == null) {
elementPath = elementPath.getParentPath();
if (elementPath != null && elementPath.getLeaf().getKind() == Tree.Kind.IMPORT) {
ImportTree impTree = (ImportTree)elementPath.getLeaf();
if (!impTree.isStatic()) {
return;
}
Tree idTree = impTree.getQualifiedIdentifier();
if (idTree.getKind() != Tree.Kind.MEMBER_SELECT) {
return;
}
final Name id = ((MemberSelectTree) idTree).getIdentifier();
if (id == null || id.contentEquals("*") || !id.contentEquals(elementToFind.getSimpleName())) { // NOI18N
// skip import static java.lang.Math.*
return;
}
Tree classTree = ((MemberSelectTree) idTree).getExpression();
elementPath = trees.getPath(workingCopy.getCompilationUnit(), classTree);
el = trees.getElement(elementPath);
if (el == null) {
return;
}
Iterator iter = workingCopy.getElementUtilities().getMembers(el.asType(),new ElementUtilities.ElementAcceptor() {
@Override
public boolean accept(Element e, TypeMirror type) {
return id.equals(e.getSimpleName());
}
}).iterator();
if (iter.hasNext()) {
el = (Element) iter.next();
if(iter.hasNext()) {
if(el.equals(elementToFind) || isMethodMatch(el)) {
newImports.add(make.Import(make.QualIdent((Element) iter.next()), true));
} else {
newImports.add(make.Import(make.QualIdent(el), true));
do {
el = (Element) iter.next();
} while (iter.hasNext() && el != null && !(el.equals(elementToFind) || isMethodMatch(el)));
if(el == null) {
return;
}
}
}
}
} else {
return;
}
}
if (el.equals(elementToFind) || isMethodMatch(el)) {
String useThis = null;
String useSuper = null;
if (elementToFind!=null && elementToFind.getKind().isField()) {
Scope scope = workingCopy.getTrees().getScope(elementPath);
for (Element ele : scope.getLocalElements()) {
if ((ele.getKind() == ElementKind.LOCAL_VARIABLE || ele.getKind() == ElementKind.PARAMETER)
&& ele.getSimpleName().toString().equals(newName)) {
if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
String isThis = ((MemberSelectTree) tree).getExpression().toString();
if (isThis.equals("this") || isThis.endsWith(".this")) { // NOI18N
break;
}
}
if (elementToFind.getModifiers().contains(Modifier.STATIC)) {
useThis = elementToFind.getEnclosingElement().getSimpleName().toString() + ".";
} else {
Types types = workingCopy.getTypes();
if (types.isSubtype(scope.getEnclosingClass().asType(), elementToFind.getEnclosingElement().asType())) {
useThis = "this."; // NOI18N
} else {
useThis = elementToFind.getEnclosingElement().getSimpleName() + ".this."; // NOI18N
}
}
break;
}
}
}
if (elementToFind!=null && elementToFind.getKind().isField() || elementToFind.getKind().equals(ElementKind.METHOD)) {
Scope scope = workingCopy.getTrees().getScope(elementPath);
TypeElement enclosingTypeElement = scope.getEnclosingClass();
TypeMirror superclass = enclosingTypeElement==null ? null:enclosingTypeElement.getSuperclass();
Types types = workingCopy.getTypes();
if(superclass!=null && !types.isSameType(types.getNoType(TypeKind.NONE), superclass) &&
types.isSubtype(superclass, elementToFind.getEnclosingElement().asType())) {
if(elementToFind.getKind().isField()) {
for (Element ele : enclosingTypeElement.getEnclosedElements()) {
if (ele.getKind().isField() && ele.getSimpleName().toString().equals(newName)) {
if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
String isSuper = ((MemberSelectTree) tree).getExpression().toString();
if (isSuper.equals("super") || isSuper.endsWith(".super")) { // NOI18N
break;
}
}
if (types.isSubtype(enclosingTypeElement.asType(), elementToFind.getEnclosingElement().asType())) {
useSuper = "super."; // NOI18N
} else {
useSuper = elementToFind.getEnclosingElement().getSimpleName() + ".super."; // NOI18N
}
break;
}
}
} else if(elementToFind.getKind() == ElementKind.METHOD) {
ElementUtilities utils = workingCopy.getElementUtilities();
if(utils.alreadyDefinedIn((CharSequence) newName, (ExecutableType) elementToFind.asType(), (TypeElement) enclosingTypeElement)) {
boolean isSuper = false;;
if (tree.getKind() == Tree.Kind.MEMBER_SELECT) {
String superString = ((MemberSelectTree) tree).getExpression().toString();
if (superString.equals("super") || superString.endsWith(".super")) { // NOI18N
isSuper = true;
}
}
if(!isSuper) {
if (types.isSubtype(enclosingTypeElement.asType(), elementToFind.getEnclosingElement().asType())) {
useSuper = "super."; // NOI18N
} else {
useSuper = elementToFind.getEnclosingElement().getSimpleName() + ".super."; // NOI18N
}
}
}
}
}
}
Tree nju = null;
if (useThis!=null) {
nju = make.setLabel(tree, useThis + newName);
} else if (useSuper !=null) {
nju = make.setLabel(tree, useSuper + newName);
} else if(elementToFind.getKind().isClass()) {
boolean duplicate = duplicateDeclaration();
final TreePath parentPath = path.getParentPath();
if(parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.IMPORT) {
ImportTree importTree = (ImportTree) parentPath.getLeaf();
if(duplicate) {
nju = make.removeCompUnitImport(workingCopy.getCompilationUnit(), importTree);
tree = workingCopy.getCompilationUnit();
} else {
nju = make.setLabel(tree, newName);
} } else {
if(duplicate) {
nju = make.QualIdent(make.setLabel(make.QualIdent(elementToFind), newName).toString());
} else {
nju = make.setLabel(tree, newName);
}
}
} else {
final TreePath parentPath = path.getParentPath();
if(parentPath != null && parentPath.getLeaf().getKind() == Tree.Kind.IMPORT) {
ImportTree importTree = (ImportTree) parentPath.getLeaf();
imports.put(importTree, make.Import(make.setLabel(tree, newName), importTree.isStatic()));
} else {
nju = make.setLabel(tree, newName);
}
}
if(nju != null) {
rewrite(tree, nju);
}
}
}
private void renameShadowIfMatch(final TreePath path, Tree tree, Element elementToFind) {
if (shadowed == null || JavaPluginUtils.isSyntheticPath(workingCopy, path) || (handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT)) {
return;
}
TreePath elementPath = path;
Element el = workingCopy.getTrees().getElement(elementPath);
if (el == null) {
// fail fast
return;
}
for (Element shadow : shadowed) {
if (shadow.equals(el)) {
if (elementToFind.getModifiers().contains(Modifier.STATIC)) {
rewrite(tree, make.MemberSelect(make.QualIdent(el.getEnclosingElement()), shadow));
} else {
rewrite(tree, make.MemberSelect(make.MemberSelect(make.QualIdent(el.getEnclosingElement()), "this"), shadow));
}
break;
}
}
}
@Override
public Tree visitMethod(MethodTree tree, Element p) {
renameDeclIfMatch(getCurrentPath(), tree, p);
return super.visitMethod(tree, p);
}
@Override
public Tree visitClass(ClassTree tree, final Element p) {
final TreePath currentPath = getCurrentPath();
renameDeclIfMatch(getCurrentPath(), tree, p);
Element el = workingCopy.getTrees().getElement(currentPath);
if (el != null && el.getEnclosedElements().contains(p)) {
Trees trees = workingCopy.getTrees();
Scope scope = trees.getScope(trees.getPath(p));
shadowed = workingCopy.getElementUtilities().getLocalMembersAndVars(scope, new ElementUtilities.ElementAcceptor() {
@Override
public boolean accept(Element element, TypeMirror type) {
return !element.equals(p) && element.getKind().equals(p.getKind()) && element.getSimpleName().contentEquals(newName);
}
});
}
Tree value = super.visitClass(tree, p);
shadowed = null;
return value;
}
@Override
public Tree visitVariable(VariableTree tree, Element p) {
renameDeclIfMatch(getCurrentPath(), tree, p);
return super.visitVariable(tree, p);
}
@Override
public Tree visitTypeParameter(TypeParameterTree arg0, Element arg1) {
renameDeclIfMatch(getCurrentPath(), arg0, arg1);
return super.visitTypeParameter(arg0, arg1);
}
private void renameDeclIfMatch(TreePath path, Tree tree, Element elementToFind) {
if (JavaPluginUtils.isSyntheticPath(workingCopy, path) || (handle != null && handle.getKind() == Tree.Kind.LABELED_STATEMENT)) {
return;
}
Element el = workingCopy.getTrees().getElement(path);
if (el==null) {
return;
}
if (el.equals(elementToFind) || isMethodMatch(el)) {
Tree nju = make.setLabel(tree, newName);
rewrite(tree, nju);
return;
}
}
private boolean isMethodMatch(Element method) {
if (method.getKind() == ElementKind.METHOD && allMethods !=null) {
for (ElementHandle<ExecutableElement> mh: allMethods) {
ExecutableElement baseMethod = mh.resolve(workingCopy);
if (baseMethod==null) {
Logger.getLogger("org.netbeans.modules.refactoring.java").info("RenameTransformer cannot resolve " + mh);
continue;
}
if (baseMethod.equals(method) || workingCopy.getElements().overrides((ExecutableElement)method, baseMethod, workingCopy.getElementUtilities().enclosingTypeElement(baseMethod))) {
return true;
}
}
}
return false;
}
@Override
public DocTree visitReference(ReferenceTree node, Element elementToFind) {
DocTreePath currentDocPath = getCurrentDocPath();
DocTrees trees = workingCopy.getDocTrees();
Element el = trees.getElement(currentDocPath);
ExpressionTree classReference = workingCopy.getTreeUtilities().getReferenceClass(currentDocPath);
if((el == null || !(el.equals(elementToFind) || isMethodMatch(el))) && classReference != null) {
el = trees.getElement(new TreePath(getCurrentPath(), classReference));
}
if (el != null && (el.equals(elementToFind) || isMethodMatch(el))) {
ReferenceTree newRef;
Name memberName = workingCopy.getTreeUtilities().getReferenceName(currentDocPath);
List<? extends Tree> methodParameters = workingCopy.getTreeUtilities().getReferenceParameters(currentDocPath);
if(el.getKind().isClass() || el.getKind().isInterface()) {
newRef = make.Reference(make.setLabel(classReference, newName), memberName, methodParameters);
} else {
newRef = make.Reference(classReference, newName, methodParameters);
}
rewrite(currentDocPath.getTreePath().getLeaf(), node, newRef);
}
return super.visitReference(node, elementToFind);
}
@Override
public DocTree visitText(TextTree node, Element p) {
if(renameInComments && refactoring.getContext().lookup(RenamePropertyRefactoringPlugin.class) == null) {
DocTreePath currentDocPath = getCurrentDocPath();
if(p.getKind() == ElementKind.PARAMETER) {
VariableElement var = (VariableElement) p;
Element method = workingCopy.getTrees().getElement(currentDocPath.getTreePath());
if(!var.getEnclosingElement().equals(method)) {
return super.visitText(node, p);
}
}
String originalName = getOldSimpleName(p);
if(node.getBody().contains(originalName)) {
StringBuilder text = new StringBuilder(node.getBody());
for (int index = text.indexOf(originalName); index != -1; index = text.indexOf(originalName, index + 1)) {
if (index > 0 && Character.isJavaIdentifierPart(text.charAt(index - 1))) {
continue;
}
if ((index + originalName.length() < text.length()) && Character.isJavaIdentifierPart(text.charAt(index + originalName.length()))) {
continue;
}
text.delete(index, index + originalName.length());
text.insert(index, newName);
}
if(!node.getBody().contentEquals(text)) {
TextTree newText = make.Text(text.toString());
rewrite(currentDocPath.getTreePath().getLeaf(), node, newText);
}
}
}
return super.visitText(node, p);
}
@Override
public DocTree visitIdentifier(com.sun.source.doctree.IdentifierTree node, Element elementToFind) {
DocTreePath currentDocPath = getCurrentDocPath();
DocTrees trees = workingCopy.getDocTrees();
Element el = trees.getElement(currentDocPath);
if (el != null && (el.equals(elementToFind))) {
com.sun.source.doctree.IdentifierTree newIdent = make.DocIdentifier(newName);
rewrite(currentDocPath.getTreePath().getLeaf(), node, newIdent);
}
return super.visitIdentifier(node, elementToFind);
}
/**
* Renames the method (or constructor) parameter in comments. This method
* considers comments before and inside the method declaration, and within
* the method body.
*
* @param parameter the method or constructor parameter {@link Element}
*/
private void renameParameterInMethodComments(final Element parameter) {
if(refactoring.getContext().lookup(RenamePropertyRefactoringPlugin.class) != null) {
return; // XXX: Hack, do not update comments twice
}
final Tree method = workingCopy.getTrees().getPath(parameter).getParentPath().getLeaf();
final String originalName = getOldSimpleName(parameter);
final int methodStart = (int) workingCopy.getTrees().getSourcePositions()
.getStartPosition(workingCopy.getCompilationUnit(), method);
final TokenSequence<JavaTokenId> tokenSequence = workingCopy.getTokenHierarchy().tokenSequence(JavaTokenId.language());
//renaming in comments before the method/constructor
tokenSequence.move(methodStart);
while (tokenSequence.movePrevious()) {
final Token<JavaTokenId> token = tokenSequence.token();
if (isComment(token)) {
rewriteAllInComment(token.text().toString(), tokenSequence.offset(), originalName);
} else if (token.id() != WHITESPACE) {
break;
}
}
//renaming in comments within the method/constructor declaration and body
final int methodEnd = (int) workingCopy.getTrees().getSourcePositions()
.getEndPosition(workingCopy.getCompilationUnit(), method);
tokenSequence.move(methodStart);
while (tokenSequence.moveNext() && tokenSequence.offset() < methodEnd) {
final Token<JavaTokenId> token = tokenSequence.token();
if (isComment(token)) {
rewriteAllInComment(token.text().toString(), tokenSequence.offset(), originalName);
}
}
}
/**
* Checks if {@code token} represents a comment.
*
* @param token the {@link Token} to check
* @return {@code true} if {@code token} represents a line comment, block
* comment; {@code false} otherwise or javadoc.
*/
private boolean isComment(final Token<JavaTokenId> token) {
switch (token.id()) {
case LINE_COMMENT:
case BLOCK_COMMENT:
return true;
case JAVADOC_COMMENT:
default:
return false;
}
}
/**
* Changes all occurrences of {@code originalName} to the new name in the comment {@code text}.
*
* @param text the text of the comment token
* @param offset the offset of the comment token
* @param originalName the old name to change
*/
private void rewriteAllInComment(final String text, final int offset, final String originalName) {
for (int index = text.indexOf(originalName); index != -1; index = text.indexOf(originalName, index + 1)) {
if (index > 0 && Character.isJavaIdentifierPart(text.charAt(index - 1))) {
continue;
}
if ((index + originalName.length() < text.length()) && Character.isJavaIdentifierPart(text.charAt(index + originalName.length()))) {
continue;
}
//at least do not rename html start and end tags.
if (text.charAt(index-1) == '<' || text.charAt(index-1) == '/') {
continue;
}
workingCopy.rewriteInComment(offset + index, originalName.length(), newName);
}
}
private boolean duplicateDeclaration() {
ErrorAwareTreeScanner<Boolean, String> duplicateIds = new ErrorAwareTreeScanner<Boolean, String>() {
@Override public Boolean visitClass(ClassTree node, String p) {
if(node.getSimpleName().contentEquals(p)) {
return Boolean.TRUE;
}
return super.visitClass(node, p);
}
};
return Boolean.TRUE == duplicateIds.scan(workingCopy.getCompilationUnit(), newName);
}
}