blob: 9d04edddca0fe62e16642212ac87a4ae813e874b [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.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.TextTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.DocSourcePositions;
import com.sun.source.util.DocTreePath;
import com.sun.source.util.DocTreePathScanner;
import com.sun.source.util.DocTrees;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import java.util.HashSet;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Position;
import javax.tools.Diagnostic;
import org.netbeans.api.java.lexer.JavaTokenId;
import static org.netbeans.api.java.lexer.JavaTokenId.WHITESPACE;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.support.CancellableTreePathScanner;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.lib.editor.util.swing.MutablePositionRegion;
import org.openide.text.NbDocument;
import org.openide.util.Exceptions;
/**
*
* @author Jan Lahoda
*/
public class FindLocalUsagesQuery extends CancellableTreePathScanner<Void, Void> {
private CompilationInfo info;
private SourcePositions sp;
private TreeUtilities treeUtils;
private Set<MutablePositionRegion> usages;
private Set<MutablePositionRegion> comments;
private Element toFind;
private Document doc;
private DocTreePathScanner docScanner;
private boolean searchComment;
public FindLocalUsagesQuery() {
}
public void findUsages(Element element, CompilationInfo info, Document doc, boolean searchComment) {
this.info = info;
this.usages = new HashSet<>();
this.comments = new HashSet<>();
this.toFind = element;
this.doc = doc;
this.searchComment = searchComment;
this.treeUtils = info.getTreeUtilities();
this.sp = info.getTrees().getSourcePositions();
this.docScanner = new DocTreePathScannerImpl();
scan(info.getCompilationUnit(), null);
}
public Set<MutablePositionRegion> getUsages() {
return usages;
}
public Set<MutablePositionRegion> getComments() {
return comments;
}
private void handleJavadoc(TreePath el) {
if(el != null) {
switch(el.getLeaf().getKind()) {
case METHOD:
case ANNOTATION_TYPE:
case CLASS:
case ENUM:
case INTERFACE:
case VARIABLE:
DocCommentTree docCommentTree = info.getDocTrees().getDocCommentTree(el);
if(docCommentTree != null) {
DocTreePath docTreePath = new DocTreePath(el, docCommentTree);
docScanner.scan(docTreePath, null);
}
default:
break;
}
}
}
@Override
public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
if (!searchComment) {
return super.visitCompilationUnit(node, p);
}
if (toFind.getKind() == ElementKind.PARAMETER) {
renameParameterInMethodComments(toFind);
} else {
String originalName = toFind.getSimpleName().toString();
if (originalName!=null) {
TokenSequence<JavaTokenId> ts = info.getTokenHierarchy().tokenSequence(JavaTokenId.language());
while (ts.moveNext()) {
Token<JavaTokenId> t = ts.token();
if (isComment(t)) {
findAllInComment(t.text().toString(), ts.offset(), originalName);
}
}
}
}
return super.visitCompilationUnit(node, p);
}
/**
* 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) {
final Tree method = info.getTrees().getPath(parameter).getParentPath().getLeaf();
final String originalName = parameter.getSimpleName().toString();
final int methodStart = (int) info.getTrees().getSourcePositions()
.getStartPosition(info.getCompilationUnit(), method);
final TokenSequence<JavaTokenId> tokenSequence = info.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)) {
findAllInComment(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) info.getTrees().getSourcePositions()
.getEndPosition(info.getCompilationUnit(), method);
tokenSequence.move(methodStart);
while (tokenSequence.moveNext() && tokenSequence.offset() < methodEnd) {
final Token<JavaTokenId> token = tokenSequence.token();
if (isComment(token)) {
findAllInComment(token.text().toString(), tokenSequence.offset(), originalName);
}
}
}
/**
* 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 findAllInComment(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;
}
try {
MutablePositionRegion region = createRegion(doc, offset + index, offset + index + originalName.length());
comments.add(region);
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
}
/**
* 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;
}
}
@Override
public Void visitIdentifier(IdentifierTree tree, Void d) {
Element el = info.getTrees().getElement(getCurrentPath());
if (toFind.equals(el)) {
try {
long start = sp.getStartPosition(info.getCompilationUnit(), tree);
long end = sp.getEndPosition(info.getCompilationUnit(), tree);
if(start != Diagnostic.NOPOS) {
MutablePositionRegion region = createRegion(doc, (int) start, (int) end);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
return super.visitIdentifier(tree, d);
}
@Override
public Void visitMemberReference(MemberReferenceTree node, Void p) {
Element el = info.getTrees().getElement(getCurrentPath());
if (toFind.equals(el)) {
try {
int[] span = treeUtils.findNameSpan(node);
if(span != null) {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
return super.visitMemberReference(node, p);
}
@Override
public Void visitMethod(MethodTree node, Void d) {
Element el = info.getTrees().getElement(getCurrentPath());
if (toFind.equals(el)) {
try {
int[] span = treeUtils.findNameSpan(node);
if(span != null) {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
handleJavadoc(getCurrentPath());
return super.visitMethod(node, d);
}
@Override
public Void visitMemberSelect(MemberSelectTree node, Void p) {
Element el = info.getTrees().getElement(getCurrentPath());
if (toFind.equals(el)) {
try {
int[] span = treeUtils.findNameSpan(node);
if(span != null) {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
return super.visitMemberSelect(node, p);
}
@Override
public Void visitVariable(VariableTree tree, Void d) {
Element el = info.getTrees().getElement(getCurrentPath());
if (toFind.equals(el)) {
try {
int[] span = treeUtils.findNameSpan(tree);
if(span != null) {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
if (el != null && el.getKind().isField()) {
handleJavadoc(getCurrentPath());
}
return super.visitVariable(tree, d);
}
@Override
public Void visitClass(ClassTree tree, Void d) {
Element el = info.getTrees().getElement(getCurrentPath());
if (toFind.equals(el)) {
try {
int[] span = treeUtils.findNameSpan(tree);
if(span != null) {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
handleJavadoc(getCurrentPath());
return super.visitClass(tree, d);
}
@Override
public Void visitTypeParameter(TypeParameterTree node, Void p) {
Element el = info.getTrees().getElement(getCurrentPath());
if (toFind.equals(el)) {
try {
int[] span = treeUtils.findNameSpan(node);
if(span != null) {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
return super.visitTypeParameter(node, p);
}
@Override
public Void visitImport(ImportTree node, Void p) {
if (node.isStatic() && toFind.getModifiers().contains(Modifier.STATIC)) {
Tree qualIdent = node.getQualifiedIdentifier();
if (qualIdent.getKind() == Kind.MEMBER_SELECT) {
MemberSelectTree mst = (MemberSelectTree) qualIdent;
if (toFind.getSimpleName().contentEquals(mst.getIdentifier())) {
Element el = info.getTrees().getElement(new TreePath(getCurrentPath(), mst.getExpression()));
if (el != null && el.equals(toFind.getEnclosingElement())) {
try {
int[] span = treeUtils.findNameSpan(mst);
if(span != null) {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
}
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
}
}
}
return super.visitImport(node, p);
}
public static MutablePositionRegion createRegion(final Document doc, int start, int end) throws BadLocationException {
Position startPos = NbDocument.createPosition(doc, start, Position.Bias.Backward);
Position endPos = NbDocument.createPosition(doc, end, Position.Bias.Forward);
MutablePositionRegion current = new MutablePositionRegion(startPos, endPos);
return current;
}
private class DocTreePathScannerImpl extends DocTreePathScanner<DocTree, Element> {
@Override
public DocTree visitReference(ReferenceTree node, Element p) {
DocTrees trees = info.getDocTrees();
Element el = trees.getElement(getCurrentPath());
if (el != null && el.equals(toFind)) {
int[] span = treeUtils.findNameSpan(getCurrentPath().getDocComment(), node);
if(span != null) {
try {
MutablePositionRegion region = createRegion(doc, span[0], span[1]);
usages.add(region);
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
}
return super.visitReference(node, p);
}
@Override
public DocTree visitText(TextTree node, Element p) {
if(searchComment) {
DocTrees trees = info.getDocTrees();
DocSourcePositions sourcePositions = trees.getSourcePositions();
DocTreePath currentDocPath = getCurrentPath();
if(toFind.getKind() == ElementKind.PARAMETER) {
VariableElement var = (VariableElement) toFind;
Element method = trees.getElement(currentDocPath);
if(!var.getEnclosingElement().equals(method)) {
return super.visitText(node, p);
}
}
String text = node.getBody();
String name = toFind.getSimpleName().toString();
if(text.contains(name)) {
int start = (int) sourcePositions.getStartPosition(info.getCompilationUnit(), currentDocPath.getDocComment(), node);
int length = name.length();
int offset = -1;
do {
offset = text.indexOf(name, ++offset);
if(offset != -1) {
try {
MutablePositionRegion region = createRegion(doc, start + offset, start + offset + length);
comments.add(region);
} catch(BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
} while (offset != -1);
}
}
return super.visitText(node, p);
}
@Override
public DocTree visitIdentifier(com.sun.source.doctree.IdentifierTree node, Element p) {
DocTrees trees = info.getDocTrees();
Element el = trees.getElement(getCurrentPath());
if (el != null && el.equals(toFind)) {
DocSourcePositions sp = trees.getSourcePositions();
CompilationUnitTree cut = info.getCompilationUnit();
DocCommentTree docComment = getCurrentPath().getDocComment();
long start = sp.getStartPosition(cut, docComment, node);
long end = sp.getEndPosition(cut, docComment, node);
if(start != Diagnostic.NOPOS && end != Diagnostic.NOPOS) {
try {
MutablePositionRegion region = createRegion(doc, (int)start, (int)end);
usages.add(region);
} catch (BadLocationException ex) {
Exceptions.printStackTrace(ex);
}
}
}
return super.visitIdentifier(node, p);
}
}
}