/* * 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 com.google.devtools.j2objc.javac; import com.google.devtools.j2objc.ast.Javadoc; import com.google.devtools.j2objc.ast.Name; import com.google.devtools.j2objc.ast.QualifiedName; import com.google.devtools.j2objc.ast.SimpleName; import com.google.devtools.j2objc.ast.SourcePosition; import com.google.devtools.j2objc.ast.TagElement; import com.google.devtools.j2objc.ast.TagElement.TagKind; import com.google.devtools.j2objc.ast.TextElement; import com.google.devtools.j2objc.ast.TreeNode; import com.google.devtools.j2objc.util.ErrorUtil; import com.sun.source.doctree.AuthorTree; import com.sun.source.doctree.CommentTree; import com.sun.source.doctree.DeprecatedTree; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.EndElementTree; import com.sun.source.doctree.EntityTree; import com.sun.source.doctree.ErroneousTree; import com.sun.source.doctree.IdentifierTree; import com.sun.source.doctree.LinkTree; import com.sun.source.doctree.LiteralTree; import com.sun.source.doctree.ParamTree; import com.sun.source.doctree.ReferenceTree; import com.sun.source.doctree.ReturnTree; import com.sun.source.doctree.SeeTree; import com.sun.source.doctree.SinceTree; import com.sun.source.doctree.StartElementTree; import com.sun.source.doctree.TextTree; import com.sun.source.doctree.ThrowsTree; import com.sun.source.doctree.VersionTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.util.DocSourcePositions; import com.sun.source.util.DocTreeScanner; import com.sun.source.util.DocTrees; import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.DCTree; import com.sun.tools.javac.tree.JCTree; import java.util.Collections; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; /** * Converts a javac javadoc comment into a Javadoc AST node. */ class JavadocConverter extends DocTreeScanner<Void, TagElement> { private final Element element; private final DCTree.DCDocComment docComment; private final DocSourcePositions docSourcePositions; private final String source; private final CompilationUnitTree unit; private final boolean reportWarnings; private JavadocConverter(Element element, DCTree.DCDocComment docComment, String source, DocTrees docTrees, CompilationUnitTree unit, boolean reportWarnings) { this.element = element; this.docComment = docComment; this.source = source; this.docSourcePositions = docTrees.getSourcePositions(); this.unit = unit; this.reportWarnings = reportWarnings; } /** * Returns an AST node for the javadoc comment of a specified class, * method, or field element. */ static Javadoc convertJavadoc(Element element, String source, JavacEnvironment env, boolean reportWarnings) { DocTrees docTrees = DocTrees.instance(env.task()); TreePath path = docTrees.getPath(element); if (path == null) { throw new AssertionError("could not find tree path for element"); } DCTree.DCDocComment docComment = (DCTree.DCDocComment) docTrees.getDocCommentTree(path); if (docComment == null) { return null; // Declaration does not have a javadoc comment. } JavadocConverter converter = new JavadocConverter(element, docComment, source, docTrees, path.getCompilationUnit(), reportWarnings); Javadoc result = new Javadoc(); // First tag is the description. TagElement newTag = new TagElement().setTagKind(TagElement.TagKind.DESCRIPTION); converter.scan(docComment.getFirstSentence(), newTag); converter.scan(docComment.getBody(), newTag); if (!newTag.getFragments().isEmpty()) { List<TreeNode> fragments = newTag.getFragments(); int start = fragments.get(0).getStartPosition(); TreeNode lastFragment = fragments.get(fragments.size() - 1); int end = start + lastFragment.getLength(); converter.setPos(newTag, start, end); result.addTag(newTag); } for (DocTree tag : docComment.getBlockTags()) { if (tag.getKind() != DocTree.Kind.ERRONEOUS) { newTag = new TagElement(); converter.scan(tag, newTag); result.addTag(newTag); } } return result; } @Override public Void visitAuthor(AuthorTree node, TagElement tag) { setTagValues(tag, TagElement.TagKind.AUTHOR, node, node.getName()); return null; } @Override public Void visitComment(CommentTree node, TagElement tag) { tag.addFragment(setPos(node, new TextElement().setText(node.getBody()))); return null; } @Override public Void visitDeprecated(DeprecatedTree node, TagElement tag) { setTagValues(tag, TagElement.TagKind.DEPRECATED, node, node.getBody()); return null; } @Override public Void visitEndElement(EndElementTree node, TagElement tag) { String text = String.format("</%s>", node.getName().toString()); int pos = pos(node); tag.addFragment(setPos(new TextElement().setText(text), pos, pos + text.length())); return null; } @Override public Void visitEntity(EntityTree node, TagElement tag) { String text = String.format("&%s;", node.getName().toString()); tag.addFragment(setPos(node, new TextElement().setText(text))); return null; } @Override public Void visitErroneous(ErroneousTree node, TagElement tag) { if (reportWarnings) { // Update node's position to be relative to the whole source file, instead of just // the doc-comment's start. That way, the diagnostic printer will fetch the correct // text for the line the error is on. ((DCTree.DCErroneous) node).pos = ((DCTree) node).pos(docComment).getStartPosition(); ErrorUtil.warning(node.getDiagnostic().toString()); } else { // Include erroneous text in doc-comment as is. TreeNode newNode = setPos(node, new TextElement().setText(node.getBody())); tag.addFragment(newNode); } return null; } @Override public Void visitIdentifier(IdentifierTree node, TagElement tag) { tag.addFragment(setPos(node, new TextElement().setText(node.getName().toString()))); return null; } @Override public Void visitLink(LinkTree node, TagElement tag) { TagElement newTag = new TagElement().setTagKind(TagKind.parse("@" + node.getTagName())); setPos(node, newTag); if (node.getLabel().isEmpty()) { scan(node.getReference(), newTag); } else { scan(node.getLabel(), newTag); } tag.addFragment(newTag); return null; } @Override public Void visitLiteral(LiteralTree node, TagElement tag) { TagElement newTag = new TagElement(); TagKind tagKind = node.getKind() == DocTree.Kind.CODE ? TagKind.CODE : TagKind.LITERAL; setTagValues(newTag, tagKind, node, node.getBody()); tag.addFragment(newTag); return null; } @Override public Void visitParam(ParamTree node, TagElement tag) { DCTree.DCIdentifier identifier = (DCTree.DCIdentifier) node.getName(); if (identifier == null || node.isTypeParameter()) { return null; } List<? extends VariableElement> params = element instanceof ExecutableElement ? ((ExecutableElement) element).getParameters() : Collections.emptyList(); tag.setTagKind(TagElement.TagKind.PARAM); String name = identifier.toString(); VariableElement param = null; for (VariableElement p : params) { if (name.equals(p.getSimpleName().toString())) { param = p; break; } } // param will be null if the @param tag refers to a nonexistent parameter. TreeNode nameNode = param != null ? new SimpleName(param) : new SimpleName(name); setPos(identifier, nameNode); tag.addFragment(nameNode); scan(node.getDescription(), tag); int lastEnd = nameNode.getStartPosition(); for (TreeNode fragment : tag.getFragments()) { // Fix up positions to match JDT's. // TODO(tball): remove and fix JavadocGenerator after javac switch. if (fragment.getKind() == TreeNode.Kind.TEXT_ELEMENT) { TextElement text = (TextElement) fragment; text.setText(" " + text.getText()); text.setSourceRange(text.getStartPosition(), text.getLength() + 1); } int thisEnd = lastEnd + fragment.getLength(); setPos(fragment, lastEnd, thisEnd); lastEnd = thisEnd; } setPos(tag, pos(node), endPos(node)); tag.setLineNumber(nameNode.getLineNumber()); return null; } @Override public Void visitReference(ReferenceTree node, TagElement tag) { DCTree.DCReference ref = (DCTree.DCReference) node; JCTree qualifier = ref.qualifierExpression; TreeNode newNode; if (qualifier != null && qualifier.getKind() == com.sun.source.tree.Tree.Kind.MEMBER_SELECT) { newNode = convertQualifiedName(qualifier); } else { newNode = new TextElement().setText(node.getSignature()); } tag.addFragment(setPos(node, newNode)); return null; } @Override public Void visitReturn(ReturnTree node, TagElement tag) { tag.setTagKind(TagElement.TagKind.RETURN); setPos(node, tag); scan(node.getDescription(), tag); return null; } @Override public Void visitSee(SeeTree node, TagElement tag) { tag.setTagKind(TagElement.TagKind.SEE); setPos(node, tag); scan(node.getReference(), tag); return null; } @Override public Void visitSince(SinceTree node, TagElement tag) { setTagValues(tag, TagElement.TagKind.SINCE, node, node.getBody()); return null; } @Override public Void visitStartElement(StartElementTree node, TagElement tag) { StringBuilder sb = new StringBuilder("<"); sb.append(node.getName()); for (DocTree attr : node.getAttributes()) { sb.append(' '); sb.append(attr); } sb.append('>'); tag.addFragment(setPos(node, new TextElement().setText(sb.toString()))); return null; } @Override public Void visitText(TextTree node, TagElement tag) { String[] lines = node.getBody().split("\n"); int linePos = pos(node); for (String line : lines) { if (line.length() > 0) { linePos = source.indexOf(line, linePos); int endPos = linePos + line.length(); TreeNode newNode = setPos(new TextElement().setText(line), linePos, endPos); tag.addFragment(newNode); } } return null; } @Override public Void visitThrows(ThrowsTree node, TagElement tag) { setTagValues(tag, TagElement.TagKind.THROWS, node, node.getExceptionName()); scan(node.getDescription(), tag); return null; } @Override public Void visitVersion(VersionTree node, TagElement tag) { setTagValues(tag, TagElement.TagKind.VERSION, node, node.getBody()); return null; } /** * Updates a tag element with values from the javadoc node. */ private TagElement setTagValues(TagElement tag, TagKind tagKind, DocTree javadocNode, DocTree body) { tag.setTagKind(tagKind); setPos(javadocNode, tag); scan(body, tag); return tag; } private TagElement setTagValues(TagElement tag, TagKind tagKind, DocTree javadocNode, List<? extends DocTree> body) { tag.setTagKind(tagKind); setPos(javadocNode, tag); scan(body, tag); return tag; } /** * Set a TreeNode's position using the original DocTree. */ private TreeNode setPos(DocTree node, TreeNode newNode) { int pos = pos(node); return newNode.setPosition(new SourcePosition(pos, length(node), lineNumber(pos))); } /** * Set a TreeNode's position using begin and end source offsets. Its line number * is unchanged. */ private TreeNode setPos(TreeNode newNode, int pos, int endPos) { return newNode.setPosition(new SourcePosition(pos, endPos - pos, lineNumber(pos))); } private int pos(DocTree node) { return (int) docSourcePositions.getStartPosition(unit, docComment, node); } private int endPos(DocTree node) { return (int) docSourcePositions.getEndPosition(unit, docComment, node); } private int lineNumber(int pos) { return (int) unit.getLineMap().getLineNumber(pos); } private int length(DocTree node) { return endPos(node) - pos(node); } private Name convertQualifiedName(JCTree qualifier) { if (qualifier.getKind() == com.sun.source.tree.Tree.Kind.MEMBER_SELECT) { JCTree.JCFieldAccess select = (JCTree.JCFieldAccess) qualifier; return new QualifiedName() .setName(new SimpleName(select.getIdentifier().toString())) .setQualifier(convertQualifiedName(select.getExpression())); } else { return new SimpleName(qualifier.toString()); } } }