blob: 42a22dd1e3016968e35bfe24efb97d7b044ca1c9 [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.java.editor.base.javadoc;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.doctree.DocTree;
import com.sun.source.doctree.ParamTree;
import com.sun.source.doctree.ReferenceTree;
import com.sun.source.doctree.SeeTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.IdentifierTree;
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.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.TreePath;
import org.netbeans.api.java.source.support.ErrorAwareTreePathScanner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.lexer.JavadocTokenId;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
/**
*
* @author Jan Pokorsky
*/
public final class JavadocImports {
private JavadocImports() {
}
/**
* Computes all unresolved (not imported) {@link Element}s referenced by
* all javadocs of all class members of the passed java context {@code javac}.
*
* @param javac a java context to search for all top level classes and
* all their members
* @return names that have to be resoved (imported).
*/
public static Set<String> computeUnresolvedImports(CompilationInfo javac) {
UnresolvedImportScanner scanner = new UnresolvedImportScanner(javac);
scanner.scan(javac.getCompilationUnit(), null);
return scanner.unresolved;
}
/**
* Computes all {@link Element}s referenced by javadoc of the passed element
* {@code el}.
*
* @param javac a java context
* @param el an element to search
* @return referenced elements.
*/
public static Set<TypeElement> computeReferencedElements(final CompilationInfo javac, final TreePath tp) {
final DocTrees trees = javac.getDocTrees();
DocCommentTree docComment = trees.getDocCommentTree(tp);
if (docComment == null) return Collections.emptySet();
final Set<TypeElement> result = new HashSet<TypeElement>();
new DocTreePathScanner<Void, Void>() {
@Override public Void visitReference(ReferenceTree node, Void p) {
new ErrorAwareTreePathScanner<Void, Void>() {
@Override public Void visitIdentifier(IdentifierTree node, Void p) {
Element el = trees.getElement(getCurrentPath());
if (el != null && (el.getKind().isClass() || el.getKind().isInterface())) {
result.add((TypeElement) el);
}
return super.visitIdentifier(node, p);
}
public Void scan(Iterable<? extends TreePath> toAnalyze, Void p) {
for (TreePath tp : toAnalyze) {
scan(tp, p);
}
return null;
}
}.scan(referenceEmbeddedSourceNodes(javac, getCurrentPath()), null);
return super.visitReference(node, p);
}
@Override public Void visitSee(SeeTree node, Void p) {
return super.visitSee(node, p);
}
}.scan(new DocTreePath(tp, docComment), null);
return result;
}
/**
* Computes all {@link Element}s referenced by javadoc of the passed element
* {@code el}.
*
* @param javac a java context
* @param el an element to search
* @param toFind an element to find in favadoc
* @return referenced elements.
*/
public static List<Token> computeTokensOfReferencedElements(final CompilationInfo javac, final TreePath forElement, final Element toFind) {
final DocTrees trees = javac.getDocTrees();
final DocCommentTree docComment = javac.getDocTrees().getDocCommentTree(forElement);
if (docComment == null) return Collections.emptyList();
final List<Token> result = new ArrayList<Token>();
new DocTreePathScanner<Void, Void>() {
@Override public Void visitReference(ReferenceTree node, Void p) {
new ErrorAwareTreePathScanner<Void, Void>() {
@Override public Void visitIdentifier(IdentifierTree node, Void p) {
if (toFind.equals(trees.getElement(getCurrentPath()))) {
handleUsage((int) trees.getSourcePositions().getStartPosition(javac.getCompilationUnit(), node));
}
return null;
}
@Override public Void visitMemberSelect(MemberSelectTree node, Void p) {
if (toFind.equals(trees.getElement(getCurrentPath()))) {
int[] span = javac.getTreeUtilities().findNameSpan(node);
if (span != null) {
handleUsage(span[0]);
}
return null;
}
return super.visitMemberSelect(node, p);
}
public Void scan(Iterable<? extends TreePath> toAnalyze, Void p) {
for (TreePath tp : toAnalyze) {
scan(tp, p);
}
return null;
}
}.scan(referenceEmbeddedSourceNodes(javac, getCurrentPath()), null);
if (toFind.equals(trees.getElement(getCurrentPath()))) {
int[] span = javac.getTreeUtilities().findNameSpan(docComment, node);
if (span != null) {
handleUsage(span[0]);
}
return null;
}
return super.visitReference(node, p);
}
private TokenSequence<JavadocTokenId> javadoc;
private void handleUsage(int start) {
if (javadoc == null) {
javadoc = getJavadocTS(javac, start);
if (javadoc == null) {
//not really expected:
return ;
}
}
javadoc.move(start);
if (javadoc.moveNext()) {
result.add(javadoc.token());
}
}
@Override
public Void visitParam(ParamTree node, Void p) {
if ( node.getName() != null
&& toFind.equals(paramElementFor(trees.getElement(forElement), node))) {
handleUsage((int) trees.getSourcePositions().getStartPosition(javac.getCompilationUnit(), docComment, node.getName()));
return null;
}
return super.visitParam(node, p);
}
@Override public Void visitSee(SeeTree node, Void p) {
return super.visitSee(node, p);
}
}.scan(new DocTreePath(forElement, docComment), null);
return result;
}
private static Element paramElementFor(Element methodOrClass, ParamTree ptag) {
ElementKind kind = methodOrClass.getKind();
List<? extends Element> params = Collections.emptyList();
if (kind == ElementKind.METHOD || kind == ElementKind.CONSTRUCTOR) {
ExecutableElement ee = (ExecutableElement) methodOrClass;
params = ptag.isTypeParameter()
? ee.getTypeParameters()
: ee.getParameters();
} else if (kind.isClass() || kind.isInterface()) {
TypeElement te = (TypeElement) methodOrClass;
params = te.getTypeParameters();
}
for (Element param : params) {
if (param.getSimpleName().contentEquals(ptag.getName().getName())) {
return param;
}
}
return null;
}
/**
* Resolves class or member of the reference {@code (class#member)},
* parameter {@code (@param parameter)} or type parameter {@code (@param <type_param>)}
* with respect to the passed {@code offset}.
*
* @param javac a java context
* @param offset offset pointing to javadoc part to resolve
* @return the found element or {@code null}.
*/
public static Element findReferencedElement(final CompilationInfo javac, final int offset) {
final DocTrees trees = javac.getDocTrees();
final TreePath tp = JavadocCompletionUtils.findJavadoc(javac, offset);
if (tp == null) return null;
final DocCommentTree docComment = javac.getDocTrees().getDocCommentTree(tp);
if (docComment == null) return null;
final DocSourcePositions positions = trees.getSourcePositions();
final Element[] result = new Element[1];
new DocTreePathScanner<Void, Void>() {
@Override public Void scan(DocTree node, Void p) {
if ( node != null
&& positions.getStartPosition(javac.getCompilationUnit(), docComment, node) <= offset
&& positions.getEndPosition(javac.getCompilationUnit(), docComment, node) >= offset) {
return super.scan(node, p);
}
return null;
}
@Override public Void visitReference(ReferenceTree node, Void p) {
int[] span = javac.getTreeUtilities().findNameSpan(docComment, node);
if ( span != null
&& span[0] <= offset
&& span[1] >= offset) {
result[0] = trees.getElement(getCurrentPath());
return null;
}
new ErrorAwareTreePathScanner<Void, Void>() {
@Override public Void visitIdentifier(IdentifierTree node, Void p) {
if ( positions.getStartPosition(javac.getCompilationUnit(), node) <= offset
&& positions.getEndPosition(javac.getCompilationUnit(), node) >= offset) {
result[0] = trees.getElement(getCurrentPath());
}
return null;
}
@Override public Void visitMemberSelect(MemberSelectTree node, Void p) {
int[] span = javac.getTreeUtilities().findNameSpan(node);
if ( span != null
&& span[0] <= offset
&& span[1] >= offset) {
result[0] = trees.getElement(getCurrentPath());
return null;
}
return super.visitMemberSelect(node, p);
}
@Override public Void visitMemberReference(MemberReferenceTree node, Void p) {
return super.visitMemberReference(node, p);
}
public Void scan(Iterable<? extends TreePath> toAnalyze, Void p) {
for (TreePath tp : toAnalyze) {
scan(tp, p);
}
return null;
}
}.scan(referenceEmbeddedSourceNodes(javac, getCurrentPath()), null);
return super.visitReference(node, p);
}
@Override
public Void visitParam(ParamTree node, Void p) {
//XXX: getElement for the param's identifier???
if ( node.getName() != null
&& positions.getStartPosition(javac.getCompilationUnit(), docComment, node.getName()) <= offset
&& positions.getEndPosition(javac.getCompilationUnit(), docComment, node.getName()) >= offset) {
result[0] = paramElementFor(trees.getElement(tp), node);
return null;
}
return super.visitParam(node, p);
}
@Override public Void visitSee(SeeTree node, Void p) {
return super.visitSee(node, p);
}
}.scan(new DocTreePath(tp, docComment), null);
return result[0];
}
public static Token findNameTokenOfReferencedElement(final CompilationInfo javac, final int offset) {
final DocTrees trees = javac.getDocTrees();
final TreePath tp = JavadocCompletionUtils.findJavadoc(javac, offset);
if (tp == null) return null;
final DocCommentTree docComment = javac.getDocTrees().getDocCommentTree(tp);
if (docComment == null) return null;
final DocSourcePositions positions = trees.getSourcePositions();
final Token[] result = new Token[1];
new DocTreePathScanner<Void, Void>() {
@Override public Void scan(DocTree node, Void p) {
if ( node != null
&& positions.getStartPosition(javac.getCompilationUnit(), docComment, node) <= offset
&& positions.getEndPosition(javac.getCompilationUnit(), docComment, node) >= offset) {
return super.scan(node, p);
}
return null;
}
@Override public Void visitReference(ReferenceTree node, Void p) {
int[] span = javac.getTreeUtilities().findNameSpan(docComment, node);
if ( span != null
&& span[0] <= offset
&& span[1] >= offset) {
handleUsage(offset);
return null;
}
new ErrorAwareTreePathScanner<Void, Void>() {
@Override public Void visitIdentifier(IdentifierTree node, Void p) {
if ( positions.getStartPosition(javac.getCompilationUnit(), node) <= offset
&& positions.getEndPosition(javac.getCompilationUnit(), node) >= offset) {
handleUsage(offset);
}
return null;
}
@Override public Void visitMemberSelect(MemberSelectTree node, Void p) {
int[] span = javac.getTreeUtilities().findNameSpan(node);
if ( span != null
&& span[0] <= offset
&& span[1] >= offset) {
handleUsage(offset);
return null;
}
return super.visitMemberSelect(node, p);
}
@Override public Void visitMemberReference(MemberReferenceTree node, Void p) {
return super.visitMemberReference(node, p);
}
public Void scan(Iterable<? extends TreePath> toAnalyze, Void p) {
for (TreePath tp : toAnalyze) {
scan(tp, p);
}
return null;
}
}.scan(referenceEmbeddedSourceNodes(javac, getCurrentPath()), null);
return super.visitReference(node, p);
}
private void handleUsage(int start) {
TokenSequence<JavadocTokenId> javadoc = getJavadocTS(javac, start);
if (javadoc == null) {
//not really expected:
return ;
}
javadoc.move(start);
if (javadoc.moveNext()) {
result[0] = javadoc.token();
}
}
@Override
public Void visitParam(ParamTree node, Void p) {
//XXX: getElement for the param's identifier???
if ( node.getName() != null
&& positions.getStartPosition(javac.getCompilationUnit(), docComment, node.getName()) <= offset
&& positions.getEndPosition(javac.getCompilationUnit(), docComment, node.getName()) >= offset) {
result[0] = findNameTokenOfParamTag(offset, getJavadocTS(javac, offset));
return null;
}
return super.visitParam(node, p);
}
@Override public Void visitSee(SeeTree node, Void p) {
return super.visitSee(node, p);
}
}.scan(new DocTreePath(tp, docComment), null);
return result[0];
}
private static Token<JavadocTokenId> findNameTokenOfParamTag(int startPos, TokenSequence<JavadocTokenId> jdTokenSequence) {
Token<JavadocTokenId> result = null;
if (isInsideParamName(jdTokenSequence, startPos)) {
int delta = jdTokenSequence.move(startPos);
if (jdTokenSequence.moveNext() && (JavadocTokenId.IDENT == jdTokenSequence.token().id() || JavadocTokenId.HTML_TAG == jdTokenSequence.token().id())
|| delta == 0 && jdTokenSequence.movePrevious() && (JavadocTokenId.IDENT == jdTokenSequence.token().id() || JavadocTokenId.HTML_TAG == jdTokenSequence.token().id())) {
result = jdTokenSequence.token();
}
}
return result;
}
/**
* Checks if the passed position {@code pos} is inside java reference of
* some javadoc tag. This lightweight implementation ignores method parameters
*
* @param jdts javadoc token sequence to search
* @param pos position to check
* @return {@code true} if the position is inside the reference.
*/
public static boolean isInsideReference(TokenSequence<JavadocTokenId> jdts, int pos) {
int delta = jdts.move(pos);
if (jdts.moveNext() && JavadocTokenId.IDENT == jdts.token().id()
|| delta == 0 && jdts.movePrevious() && JavadocTokenId.IDENT == jdts.token().id()) {
// go back and find tag
boolean isBeforeWS = false; // is current tage before white space?
while (jdts.movePrevious()) {
Token<JavadocTokenId> jdt = jdts.token();
switch (jdt.id()) {
case DOT:
case HASH:
case IDENT:
if (isBeforeWS) {
return false;
} else {
continue;
}
case OTHER_TEXT:
isBeforeWS |= JavadocCompletionUtils.isWhiteSpace(jdt);
isBeforeWS |= JavadocCompletionUtils.isLineBreak(jdt);
if (isBeforeWS) {
continue;
} else {
return false;
}
case TAG:
return isBeforeWS && isReferenceTag(jdt);
case HTML_TAG:
return false;
default:
return false;
}
}
}
return false;
}
/**
* Checks if the passed position {@code pos} is inside name part of
* some javadoc param tag.
*
* @param jdts javadoc token sequence to search
* @param pos position to check
* @return {@code true} if the position is inside the param name.
*/
public static boolean isInsideParamName(TokenSequence<JavadocTokenId> jdts, int pos) {
int delta = jdts.move(pos);
if ((jdts.moveNext() && (JavadocTokenId.IDENT == jdts.token().id() || JavadocTokenId.HTML_TAG == jdts.token().id())
|| delta == 0 && jdts.movePrevious() && (JavadocTokenId.IDENT == jdts.token().id() || JavadocTokenId.HTML_TAG == jdts.token().id()))
&& jdts.movePrevious() && JavadocTokenId.OTHER_TEXT == jdts.token().id()
&& jdts.movePrevious() && JavadocTokenId.TAG == jdts.token().id()) {
return "@param".contentEquals(jdts.token().text());
}
return false;
}
private static final Set<String> ALL_REF_TAG_NAMES = new HashSet<String>(
Arrays.asList("@link", "@linkplain", "@value", "@see", "@throws")); // NOI18N
private static boolean isReferenceTag(Token<JavadocTokenId> tag) {
String tagName = tag.text().toString().intern();
return tag.id() == JavadocTokenId.TAG && ALL_REF_TAG_NAMES.contains(tagName);
}
private static TokenSequence<JavadocTokenId> getJavadocTS(CompilationInfo javac, int start) {
TokenSequence<JavadocTokenId> javadoc = null;
TokenSequence<JavaTokenId> ts = SourceUtils.getJavaTokenSequence(javac.getTokenHierarchy(), start);
if (ts.moveNext() && ts.token().id() == JavaTokenId.JAVADOC_COMMENT) {
javadoc = ts.embedded(JavadocTokenId.language());
}
return javadoc;
}
private static final class UnresolvedImportScanner extends ErrorAwareTreePathScanner<Void, Void> {
private final CompilationInfo javac;
private Set<String> unresolved = new HashSet<String>();
public UnresolvedImportScanner(CompilationInfo javac) {
this.javac = javac;
}
@Override
public Void visitClass(ClassTree node, Void p) {
resolveElement();
return super.visitClass(node, p);
}
@Override
public Void visitMethod(MethodTree node, Void p) {
resolveElement();
return super.visitMethod(node, p);
}
@Override
public Void visitVariable(VariableTree node, Void p) {
resolveElement();
return super.visitVariable(node, p);
}
private void resolveElement() {
final DocTrees trees = javac.getDocTrees();
DocCommentTree dcComment = trees.getDocCommentTree(getCurrentPath());
if (dcComment == null) return ;
new DocTreePathScanner<Void, Void>() {
@Override public Void visitReference(ReferenceTree node, Void p) {
new ErrorAwareTreePathScanner<Void, Void>() {
@Override public Void visitIdentifier(IdentifierTree node, Void p) {
Element el = trees.getElement(getCurrentPath());
if (el == null || el.asType().getKind() == TypeKind.ERROR) {
unresolved.add(node.getName().toString());
} else if (el.getKind() == ElementKind.PACKAGE) {
//does the package really exists? (see ComputeImports)
String s = ((PackageElement) el).getQualifiedName().toString();
if (javac.getElements().getPackageElement(s) == null) {
//probably situation like:
//Map.Entry e;
//where Map is not imported
unresolved.add(node.getName().toString());
}
}
return super.visitIdentifier(node, p);
}
public Void scan(Iterable<? extends TreePath> toAnalyze, Void p) {
for (TreePath tp : toAnalyze) {
scan(tp, p);
}
return null;
}
}.scan(referenceEmbeddedSourceNodes(javac, getCurrentPath()), null);
return super.visitReference(node, p);
}
}.scan(new DocTreePath(getCurrentPath(), dcComment), null);
}
}
private static Iterable<? extends TreePath> referenceEmbeddedSourceNodes(CompilationInfo info, DocTreePath ref) {
List<TreePath> result = new ArrayList<TreePath>();
if (info.getTreeUtilities().getReferenceClass(ref) != null) {
result.add(new TreePath(ref.getTreePath(), info.getTreeUtilities().getReferenceClass(ref)));
}
List<? extends Tree> params = info.getTreeUtilities().getReferenceParameters(ref);
if (params != null) {
for (Tree et : params) {
result.add(new TreePath(ref.getTreePath(), et));
}
}
return result;
}
}