/* * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /** * A utility class. * *

This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice. */ package jdk.javadoc.internal.doclets.toolkit; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; import javax.tools.FileObject; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; import com.sun.source.doctree.AttributeTree; import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.IdentifierTree; import com.sun.source.doctree.ParamTree; import com.sun.source.doctree.ReferenceTree; import com.sun.source.doctree.TextTree; import com.sun.source.util.DocTreeFactory; import com.sun.source.util.DocTreePath; import com.sun.source.util.DocTrees; import com.sun.source.util.TreePath; import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; import jdk.javadoc.internal.doclets.toolkit.util.Utils; public class CommentUtils { final BaseConfiguration configuration; final Utils utils; final Resources resources; final DocTreeFactory treeFactory; final HashMap dcTreesMap = new HashMap<>(); final DocTrees trees; final Elements elementUtils; protected CommentUtils(BaseConfiguration configuration) { this.configuration = configuration; utils = configuration.utils; resources = configuration.getResources(); trees = configuration.docEnv.getDocTrees(); treeFactory = trees.getDocTreeFactory(); elementUtils = configuration.docEnv.getElementUtils(); } public List makePropertyDescriptionTree(List content) { List out = new ArrayList<>(); Name name = elementUtils.getName("propertyDescription"); out.add(treeFactory.newUnknownBlockTagTree(name, content)); return out; } public List makePropertyDescriptionTree(String content) { List inlist = new ArrayList<>(); inlist.add(treeFactory.newCommentTree(content)); List out = new ArrayList<>(); Name name = elementUtils.getName("propertyDescription"); out.add(treeFactory.newUnknownBlockTagTree(name, inlist)); return out; } public List makeFirstSentenceTree(String content) { List out = new ArrayList<>(); out.add(treeFactory.newTextTree(content)); return out; } public DocTree makeSeeTree(String sig, Element e) { List list = new ArrayList<>(); list.add(treeFactory.newReferenceTree(sig)); return treeFactory.newSeeTree(list); } public TextTree makeTextTree(String content) { return treeFactory.newTextTree(content); } public TextTree makeTextTreeForResource(String key) { return treeFactory.newTextTree(resources.getText(key)); } public void setEnumValuesTree(ExecutableElement ee) { List fullBody = new ArrayList<>(); fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.fullbody"))); List descriptions = new ArrayList<>(); descriptions.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.return"))); List tags = new ArrayList<>(); tags.add(treeFactory.newReturnTree(descriptions)); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } public void setEnumValueOfTree(ExecutableElement ee) { List fullBody = new ArrayList<>(); fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.fullbody"))); List tags = new ArrayList<>(); List paramDescs = new ArrayList<>(); paramDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.param_name"))); java.util.List parameters = ee.getParameters(); VariableElement param = parameters.get(0); IdentifierTree id = treeFactory.newIdentifierTree(elementUtils.getName(param.getSimpleName().toString())); tags.add(treeFactory.newParamTree(false, id, paramDescs)); List returnDescs = new ArrayList<>(); returnDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.return"))); tags.add(treeFactory.newReturnTree(returnDescs)); List throwsDescs = new ArrayList<>(); throwsDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.throws_ila"))); ReferenceTree ref = treeFactory.newReferenceTree("java.lang.IllegalArgumentException"); tags.add(treeFactory.newThrowsTree(ref, throwsDescs)); throwsDescs = new ArrayList<>(); throwsDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.throws_npe"))); ref = treeFactory.newReferenceTree("java.lang.NullPointerException"); tags.add(treeFactory.newThrowsTree(ref, throwsDescs)); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } /** * Generates the description for the canonical constructor for a record. * @param ee the constructor */ public void setRecordConstructorTree(ExecutableElement ee) { TypeElement te = utils.getEnclosingTypeElement(ee); List fullBody = makeDescriptionWithName("doclet.record_constructor_doc.fullbody", te.getSimpleName()); List tags = new ArrayList<>(); java.util.List parameters = ee.getParameters(); for (VariableElement param : ee.getParameters()) { Name name = param.getSimpleName(); IdentifierTree id = treeFactory.newIdentifierTree(name); tags.add(treeFactory.newParamTree(false, id, makeDescriptionWithComponent("doclet.record_constructor_doc.param_name", te, name))); } DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } /** * Generates the description for the standard {@code equals} method for a record. * @param ee the {@code equals} method */ @SuppressWarnings("preview") public void setRecordEqualsTree(ExecutableElement ee) { List fullBody = new ArrayList<>(); add(fullBody, "doclet.record_equals_doc.fullbody.head"); fullBody.add(treeFactory.newTextTree(" ")); List comps = ((TypeElement) ee.getEnclosingElement()).getRecordComponents(); boolean hasPrimitiveComponents = comps.stream().anyMatch(e -> e.asType().getKind().isPrimitive()); boolean hasReferenceComponents = comps.stream().anyMatch(e -> !e.asType().getKind().isPrimitive()); if (hasPrimitiveComponents && hasReferenceComponents) { add(fullBody, "doclet.record_equals_doc.fullbody.tail.both"); } else if (hasPrimitiveComponents) { add(fullBody, "doclet.record_equals_doc.fullbody.tail.primitive"); } else if (hasReferenceComponents) { add(fullBody, "doclet.record_equals_doc.fullbody.tail.reference"); } Name paramName = ee.getParameters().get(0).getSimpleName(); IdentifierTree id = treeFactory.newIdentifierTree(paramName); List paramDesc = makeDescriptionWithName("doclet.record_equals_doc.param_name", paramName); DocTree paramTree = treeFactory.newParamTree(false, id, paramDesc); DocTree returnTree = treeFactory.newReturnTree( makeDescriptionWithName("doclet.record_equals_doc.return", paramName)); TreePath treePath = utils.getTreePath(ee.getEnclosingElement()); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(paramTree, returnTree)); dcTreesMap.put(ee, new DocCommentDuo(treePath, docTree)); } private void add(List contents, String resourceKey) { // Special case to allow '{@link ...}' to appear in the string. // A less general case would be to detect literal use of Object.equals // A more general case would be to allow access to DocCommentParser somehow String body = resources.getText(resourceKey); Pattern p = Pattern.compile("\\{@link (\\S*)(.*)}"); Matcher m = p.matcher(body); int start = 0; while (m.find(start)) { if (m.start() > start) { contents.add(treeFactory.newTextTree(body.substring(start, m.start()))); } ReferenceTree refTree = treeFactory.newReferenceTree(m.group(1)); List descr = List.of(treeFactory.newTextTree(m.group(2).trim())) ; contents.add(treeFactory.newLinkTree(refTree, descr)); start = m.end(); } if (start < body.length()) { contents.add(treeFactory.newTextTree(body.substring(start))); } } /** * Generates the description for the standard {@code hashCode} method for a record. * @param ee the {@code hashCode} method */ public void setRecordHashCodeTree(ExecutableElement ee) { List fullBody = List.of(makeTextTreeForResource("doclet.record_hashCode_doc.fullbody")); DocTree returnTree = treeFactory.newReturnTree( List.of(makeTextTreeForResource("doclet.record_hashCode_doc.return"))); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree)); dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } /** * Generates the description for the standard {@code toString} method for a record. * @param ee the {@code toString} method */ public void setRecordToStringTree(ExecutableElement ee) { List fullBody = List.of( treeFactory.newTextTree(resources.getText("doclet.record_toString_doc.fullbody"))); DocTree returnTree = treeFactory.newReturnTree(List.of( treeFactory.newTextTree(resources.getText("doclet.record_toString_doc.return")))); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree)); dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } /** * Generates the description for the accessor method for a state component of a record. * @param ee the accessor method */ public void setRecordAccessorTree(ExecutableElement ee) { TypeElement te = utils.getEnclosingTypeElement(ee); List fullBody = makeDescriptionWithComponent("doclet.record_accessor_doc.fullbody", te, ee.getSimpleName()); DocTree returnTree = treeFactory.newReturnTree( makeDescriptionWithComponent("doclet.record_accessor_doc.return", te, ee.getSimpleName())); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree)); dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } /** * Generates the description for the field for a state component of a record. * @param ve the field */ public void setRecordFieldTree(VariableElement ve) { TypeElement te = utils.getEnclosingTypeElement(ve); List fullBody = makeDescriptionWithComponent("doclet.record_field_doc.fullbody", te, ve.getSimpleName()); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of()); dcTreesMap.put(ve, new DocCommentDuo(null, docTree)); } /** * Creates a description that contains a reference to a state component of a record. * The description is looked up as a resource, and should contain {@code {0}} where the * reference to the component is to be inserted. The reference will be a link if the * doc comment for the record has a {@code @param} tag for the component. * @param key the resource key for the description * @param elem the record element * @param component the name of the component * @return the description */ private List makeDescriptionWithComponent(String key, TypeElement elem, Name component) { List result = new ArrayList<>(); String text = resources.getText(key); int index = text.indexOf("{0}"); result.add(treeFactory.newTextTree(text.substring(0, index))); Name A = elementUtils.getName("a"); Name CODE = elementUtils.getName("code"); Name HREF = elementUtils.getName("href"); List code = List.of( treeFactory.newStartElementTree(CODE, List.of(), false), treeFactory.newTextTree(component.toString()), treeFactory.newEndElementTree(CODE)); if (hasParamForComponent(elem, component)) { DocTree href = treeFactory.newAttributeTree(HREF, AttributeTree.ValueKind.DOUBLE, List.of(treeFactory.newTextTree("#param-" + component))); result.add(treeFactory.newStartElementTree(A, List.of(href), false)); result.addAll(code); result.add(treeFactory.newEndElementTree(A)); } else { result.addAll(code); } result.add(treeFactory.newTextTree(text.substring(index + 3))); return result; } /** * Returns whether or not the doc comment for a record contains an {@code @param}} * for a state component of the record. * @param elem the record element * @param component the name of the component * @return whether or not there is a {@code @param}} for the component */ private boolean hasParamForComponent(TypeElement elem, Name component) { DocCommentTree elemComment = utils.getDocCommentTree(elem); if (elemComment == null) { return false; } for (DocTree t : elemComment.getBlockTags()) { if (t instanceof ParamTree && ((ParamTree) t).getName().getName() == component) { return true; } } return false; } /** * Creates a description that contains the simple name of a program element * The description is looked up as a resource, and should contain {@code {0}} where the * name is to be inserted. T * @param key the resource key for the description * @param name the name * @return the description */ private List makeDescriptionWithName(String key, Name name) { String text = resources.getText(key); int index = text.indexOf("{0}"); if (index == -1) { return List.of(treeFactory.newTextTree(text)); } else { Name CODE = elementUtils.getName("code"); return List.of( treeFactory.newTextTree(text.substring(0, index)), treeFactory.newStartElementTree(CODE, List.of(), false), treeFactory.newTextTree(name.toString()), treeFactory.newEndElementTree(CODE), treeFactory.newTextTree(text.substring(index + 3)) ); } } /* * Returns the TreePath/DocCommentTree duo for synthesized element. */ public DocCommentDuo getSyntheticCommentDuo(Element e) { return dcTreesMap.get(e); } /* * Returns the TreePath/DocCommentTree duo for html sources. */ public DocCommentDuo getHtmlCommentDuo(Element e) { FileObject fo = null; PackageElement pe = null; switch (e.getKind()) { case OTHER: if (e instanceof DocletElement) { DocletElement de = (DocletElement)e; fo = de.getFileObject(); pe = de.getPackageElement(); } break; case PACKAGE: fo = configuration.workArounds.getJavaFileObject((PackageElement)e); pe = (PackageElement)e; break; default: return null; } if (fo == null) { return null; } DocCommentTree dcTree = trees.getDocCommentTree(fo); if (dcTree == null) { return null; } DocTreePath treePath = trees.getDocTreePath(fo, pe); return new DocCommentDuo(treePath.getTreePath(), dcTree); } public DocCommentTree parse(URI uri, String text) { return trees.getDocCommentTree(new SimpleJavaFileObject( uri, JavaFileObject.Kind.SOURCE) { @Override @DefinedBy(Api.COMPILER) public CharSequence getCharContent(boolean ignoreEncoding) { return text; } }); } public void setDocCommentTree(Element element, List fullBody, List blockTags) { DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, blockTags); dcTreesMap.put(element, new DocCommentDuo(null, docTree)); // A method having null comment (no comment) that might need to be replaced // with a synthetic comment, remove such a comment from the cache. utils.removeCommentHelper(element); } /** * A simplistic container to transport a TreePath, DocCommentTree pair. * Here is why we need this: * a. not desirable to add javac's pair. * b. DocTreePath is not a viable option either, as a null TreePath is required * to represent synthetic comments for Enum.values, valuesOf, javafx properties. */ public static class DocCommentDuo { public final TreePath treePath; public final DocCommentTree dcTree; public DocCommentDuo(TreePath treePath, DocCommentTree dcTree) { this.treePath = treePath; this.dcTree = dcTree; } } }