--- old/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java 2019-10-30 16:23:57.743748183 -0700 +++ new/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java 2019-10-30 16:23:57.375748196 -0700 @@ -38,20 +38,26 @@ 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; @@ -65,6 +71,7 @@ public class CommentUtils { final BaseConfiguration configuration; + final Utils utils; final Resources resources; final DocTreeFactory treeFactory; final HashMap dcTreesMap = new HashMap<>(); @@ -73,6 +80,7 @@ protected CommentUtils(BaseConfiguration configuration) { this.configuration = configuration; + utils = configuration.utils; resources = configuration.getResources(); trees = configuration.docEnv.getDocTrees(); treeFactory = trees.getDocTreeFactory(); @@ -107,17 +115,17 @@ return treeFactory.newSeeTree(list); } - public DocTree makeTextTree(String content) { - TextTree text = treeFactory.newTextTree(content); - return (DocTree) text; + public TextTree makeTextTree(String content) { + return treeFactory.newTextTree(content); } - public void setEnumValuesTree(Element e) { - Utils utils = configuration.utils; - String klassName = utils.getSimpleName(utils.getEnclosingTypeElement(e)); + 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", klassName))); + 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"))); @@ -125,11 +133,10 @@ List tags = new ArrayList<>(); tags.add(treeFactory.newReturnTree(descriptions)); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); - dcTreesMap.put(e, new DocCommentDuo(null, docTree)); + dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } - public void setEnumValueOfTree(Element e) { - + public void setEnumValueOfTree(ExecutableElement ee) { List fullBody = new ArrayList<>(); fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.fullbody"))); @@ -137,7 +144,6 @@ List paramDescs = new ArrayList<>(); paramDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.param_name"))); - ExecutableElement ee = (ExecutableElement) e; java.util.List parameters = ee.getParameters(); VariableElement param = parameters.get(0); IdentifierTree id = treeFactory.newIdentifierTree(elementUtils.getName(param.getSimpleName().toString())); @@ -161,7 +167,231 @@ DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); - dcTreesMap.put(e, new DocCommentDuo(null, docTree)); + 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)) + ); + } } /* @@ -215,7 +445,7 @@ } public void setDocCommentTree(Element element, List fullBody, - List blockTags, Utils utils) { + 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