1 /*
   2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /**
  27  *  A utility class.
  28  *
  29  *  <p><b>This is NOT part of any supported API.
  30  *  If you write code that depends on this, you do so at your own risk.
  31  *  This code and its internal interfaces are subject to change or
  32  *  deletion without notice.</b>
  33  */
  34 
  35 package jdk.javadoc.internal.doclets.toolkit;
  36 
  37 import java.net.URI;
  38 import java.util.ArrayList;
  39 import java.util.HashMap;
  40 import java.util.List;
  41 import java.util.regex.Matcher;
  42 import java.util.regex.Pattern;
  43 
  44 import javax.lang.model.element.Element;
  45 import javax.lang.model.element.ExecutableElement;
  46 import javax.lang.model.element.Name;
  47 import javax.lang.model.element.PackageElement;
  48 import javax.lang.model.element.RecordComponentElement;
  49 import javax.lang.model.element.TypeElement;
  50 import javax.lang.model.element.VariableElement;
  51 import javax.lang.model.util.Elements;
  52 import javax.tools.FileObject;
  53 import javax.tools.JavaFileObject;
  54 import javax.tools.SimpleJavaFileObject;
  55 
  56 import com.sun.source.doctree.AttributeTree;
  57 import com.sun.source.doctree.DocCommentTree;
  58 import com.sun.source.doctree.DocTree;
  59 import com.sun.source.doctree.IdentifierTree;
  60 import com.sun.source.doctree.ParamTree;
  61 import com.sun.source.doctree.ReferenceTree;
  62 import com.sun.source.doctree.TextTree;
  63 import com.sun.source.util.DocTreeFactory;
  64 import com.sun.source.util.DocTreePath;
  65 import com.sun.source.util.DocTrees;
  66 import com.sun.source.util.TreePath;
  67 import com.sun.tools.javac.util.DefinedBy;
  68 import com.sun.tools.javac.util.DefinedBy.Api;
  69 import jdk.javadoc.internal.doclets.toolkit.util.Utils;
  70 
  71 public class CommentUtils {
  72 
  73     final BaseConfiguration configuration;
  74     final Utils utils;
  75     final Resources resources;
  76     final DocTreeFactory treeFactory;
  77     final HashMap<Element, DocCommentDuo> dcTreesMap = new HashMap<>();
  78     final DocTrees trees;
  79     final Elements elementUtils;
  80 
  81     protected CommentUtils(BaseConfiguration configuration) {
  82         this.configuration = configuration;
  83         utils = configuration.utils;
  84         resources = configuration.getResources();
  85         trees = configuration.docEnv.getDocTrees();
  86         treeFactory = trees.getDocTreeFactory();
  87         elementUtils = configuration.docEnv.getElementUtils();
  88     }
  89 
  90     public List<? extends DocTree> makePropertyDescriptionTree(List<? extends DocTree> content) {
  91         List<DocTree> out = new ArrayList<>();
  92         Name name = elementUtils.getName("propertyDescription");
  93         out.add(treeFactory.newUnknownBlockTagTree(name, content));
  94         return out;
  95     }
  96 
  97     public List<? extends DocTree> makePropertyDescriptionTree(String content) {
  98         List<DocTree> inlist = new ArrayList<>();
  99         inlist.add(treeFactory.newCommentTree(content));
 100         List<DocTree> out = new ArrayList<>();
 101         Name name = elementUtils.getName("propertyDescription");
 102         out.add(treeFactory.newUnknownBlockTagTree(name, inlist));
 103         return out;
 104     }
 105 
 106     public List<? extends DocTree> makeFirstSentenceTree(String content) {
 107         List<DocTree> out = new ArrayList<>();
 108         out.add(treeFactory.newTextTree(content));
 109         return out;
 110     }
 111 
 112     public DocTree makeSeeTree(String sig, Element e) {
 113         List<DocTree> list = new ArrayList<>();
 114         list.add(treeFactory.newReferenceTree(sig));
 115         return treeFactory.newSeeTree(list);
 116     }
 117 
 118     public TextTree makeTextTree(String content) {
 119         return treeFactory.newTextTree(content);
 120     }
 121 
 122     public TextTree makeTextTreeForResource(String key) {
 123         return treeFactory.newTextTree(resources.getText(key));
 124     }
 125 
 126     public void setEnumValuesTree(ExecutableElement ee) {
 127         List<DocTree> fullBody = new ArrayList<>();
 128         fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.fullbody")));
 129 
 130         List<DocTree> descriptions = new ArrayList<>();
 131         descriptions.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.return")));
 132 
 133         List<DocTree> tags = new ArrayList<>();
 134         tags.add(treeFactory.newReturnTree(descriptions));
 135         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags);
 136         dcTreesMap.put(ee, new DocCommentDuo(null, docTree));
 137     }
 138 
 139     public void setEnumValueOfTree(ExecutableElement ee) {
 140         List<DocTree> fullBody = new ArrayList<>();
 141         fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.fullbody")));
 142 
 143         List<DocTree> tags = new ArrayList<>();
 144 
 145         List<DocTree> paramDescs = new ArrayList<>();
 146         paramDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.param_name")));
 147         java.util.List<? extends VariableElement> parameters = ee.getParameters();
 148         VariableElement param = parameters.get(0);
 149         IdentifierTree id = treeFactory.newIdentifierTree(elementUtils.getName(param.getSimpleName().toString()));
 150         tags.add(treeFactory.newParamTree(false, id, paramDescs));
 151 
 152         List<DocTree> returnDescs = new ArrayList<>();
 153         returnDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.return")));
 154         tags.add(treeFactory.newReturnTree(returnDescs));
 155 
 156         List<DocTree> throwsDescs = new ArrayList<>();
 157         throwsDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.throws_ila")));
 158 
 159         ReferenceTree ref = treeFactory.newReferenceTree("java.lang.IllegalArgumentException");
 160         tags.add(treeFactory.newThrowsTree(ref, throwsDescs));
 161 
 162         throwsDescs = new ArrayList<>();
 163         throwsDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.throws_npe")));
 164 
 165         ref = treeFactory.newReferenceTree("java.lang.NullPointerException");
 166         tags.add(treeFactory.newThrowsTree(ref, throwsDescs));
 167 
 168         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags);
 169 
 170         dcTreesMap.put(ee, new DocCommentDuo(null, docTree));
 171     }
 172 
 173     /**
 174      * Generates the description for the canonical constructor for a record.
 175      * @param ee the constructor
 176      */
 177     public void setRecordConstructorTree(ExecutableElement ee) {
 178         TypeElement te = utils.getEnclosingTypeElement(ee);
 179 
 180         List<DocTree> fullBody =
 181                 makeDescriptionWithName("doclet.record_constructor_doc.fullbody", te.getSimpleName());
 182 
 183         List<DocTree> tags = new ArrayList<>();
 184         java.util.List<? extends VariableElement> parameters = ee.getParameters();
 185         for (VariableElement param : ee.getParameters()) {
 186             Name name = param.getSimpleName();
 187             IdentifierTree id = treeFactory.newIdentifierTree(name);
 188             tags.add(treeFactory.newParamTree(false, id,
 189                     makeDescriptionWithComponent("doclet.record_constructor_doc.param_name", te, name)));
 190         }
 191 
 192         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags);
 193         dcTreesMap.put(ee, new DocCommentDuo(null, docTree));
 194     }
 195 
 196     /**
 197      * Generates the description for the standard {@code equals} method for a record.
 198      * @param ee the {@code equals} method
 199      */
 200     @SuppressWarnings("preview")
 201     public void setRecordEqualsTree(ExecutableElement ee) {
 202         List<DocTree> fullBody = new ArrayList<>();
 203         add(fullBody, "doclet.record_equals_doc.fullbody.head");
 204         fullBody.add(treeFactory.newTextTree(" "));
 205 
 206         List<? extends RecordComponentElement> comps = ((TypeElement) ee.getEnclosingElement()).getRecordComponents();
 207         boolean hasPrimitiveComponents =
 208                 comps.stream().anyMatch(e -> e.asType().getKind().isPrimitive());
 209         boolean hasReferenceComponents =
 210                 comps.stream().anyMatch(e -> !e.asType().getKind().isPrimitive());
 211         if (hasPrimitiveComponents && hasReferenceComponents) {
 212             add(fullBody, "doclet.record_equals_doc.fullbody.tail.both");
 213         } else if (hasPrimitiveComponents) {
 214             add(fullBody, "doclet.record_equals_doc.fullbody.tail.primitive");
 215         } else if (hasReferenceComponents) {
 216             add(fullBody, "doclet.record_equals_doc.fullbody.tail.reference");
 217         }
 218         Name paramName = ee.getParameters().get(0).getSimpleName();
 219         IdentifierTree id = treeFactory.newIdentifierTree(paramName);
 220         List<DocTree> paramDesc =
 221                 makeDescriptionWithName("doclet.record_equals_doc.param_name", paramName);
 222         DocTree paramTree = treeFactory.newParamTree(false, id, paramDesc);
 223 
 224         DocTree returnTree = treeFactory.newReturnTree(
 225                 makeDescriptionWithName("doclet.record_equals_doc.return", paramName));
 226 
 227         TreePath treePath = utils.getTreePath(ee.getEnclosingElement());
 228         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(paramTree, returnTree));
 229         dcTreesMap.put(ee, new DocCommentDuo(treePath, docTree));
 230     }
 231 
 232     private void add(List<DocTree> contents, String resourceKey) {
 233         // Special case to allow '{@link ...}' to appear in the string.
 234         // A less general case would be to detect literal use of Object.equals
 235         // A more general case would be to allow access to DocCommentParser somehow
 236         String body = resources.getText(resourceKey);
 237         Pattern p = Pattern.compile("\\{@link (\\S*)(.*)}");
 238         Matcher m = p.matcher(body);
 239         int start = 0;
 240         while (m.find(start)) {
 241             if (m.start() > start) {
 242                 contents.add(treeFactory.newTextTree(body.substring(start, m.start())));
 243             }
 244             ReferenceTree refTree = treeFactory.newReferenceTree(m.group(1));
 245             List<DocTree> descr = List.of(treeFactory.newTextTree(m.group(2).trim())) ;
 246             contents.add(treeFactory.newLinkTree(refTree, descr));
 247             start = m.end();
 248         }
 249         if (start < body.length()) {
 250             contents.add(treeFactory.newTextTree(body.substring(start)));
 251         }
 252     }
 253 
 254     /**
 255      * Generates the description for the standard {@code hashCode} method for a record.
 256      * @param ee the {@code hashCode} method
 257      */
 258     public void setRecordHashCodeTree(ExecutableElement ee) {
 259         List<DocTree> fullBody = List.of(makeTextTreeForResource("doclet.record_hashCode_doc.fullbody"));
 260 
 261         DocTree returnTree = treeFactory.newReturnTree(
 262                 List.of(makeTextTreeForResource("doclet.record_hashCode_doc.return")));
 263 
 264         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree));
 265         dcTreesMap.put(ee, new DocCommentDuo(null, docTree));
 266     }
 267 
 268     /**
 269      * Generates the description for the standard {@code toString} method for a record.
 270      * @param ee the {@code toString} method
 271      */
 272     public void setRecordToStringTree(ExecutableElement ee) {
 273         List<DocTree> fullBody = List.of(
 274                 treeFactory.newTextTree(resources.getText("doclet.record_toString_doc.fullbody")));
 275 
 276         DocTree returnTree = treeFactory.newReturnTree(List.of(
 277                 treeFactory.newTextTree(resources.getText("doclet.record_toString_doc.return"))));
 278 
 279         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree));
 280         dcTreesMap.put(ee, new DocCommentDuo(null, docTree));
 281     }
 282 
 283     /**
 284      * Generates the description for the accessor method for a state component of a record.
 285      * @param ee the accessor method
 286      */
 287     public void setRecordAccessorTree(ExecutableElement ee) {
 288         TypeElement te = utils.getEnclosingTypeElement(ee);
 289 
 290         List<DocTree> fullBody =
 291                 makeDescriptionWithComponent("doclet.record_accessor_doc.fullbody", te, ee.getSimpleName());
 292 
 293         DocTree returnTree = treeFactory.newReturnTree(
 294                     makeDescriptionWithComponent("doclet.record_accessor_doc.return", te, ee.getSimpleName()));
 295 
 296         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree));
 297         dcTreesMap.put(ee, new DocCommentDuo(null, docTree));
 298     }
 299 
 300     /**
 301      * Generates the description for the field for a state component of a record.
 302      * @param ve the field
 303      */
 304     public void setRecordFieldTree(VariableElement ve) {
 305         TypeElement te = utils.getEnclosingTypeElement(ve);
 306 
 307         List<DocTree> fullBody =
 308             makeDescriptionWithComponent("doclet.record_field_doc.fullbody", te, ve.getSimpleName());
 309 
 310         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of());
 311         dcTreesMap.put(ve, new DocCommentDuo(null, docTree));
 312     }
 313 
 314     /**
 315      * Creates a description that contains a reference to a state component of a record.
 316      * The description is looked up as a resource, and should contain {@code {0}} where the
 317      * reference to the component is to be inserted. The reference will be a link if the
 318      * doc comment for the record has a {@code @param} tag for the component.
 319      * @param key the resource key for the description
 320      * @param elem the record element
 321      * @param component the name of the component
 322      * @return the description
 323      */
 324     private List<DocTree> makeDescriptionWithComponent(String key, TypeElement elem, Name component) {
 325         List<DocTree> result = new ArrayList<>();
 326         String text = resources.getText(key);
 327         int index = text.indexOf("{0}");
 328         result.add(treeFactory.newTextTree(text.substring(0, index)));
 329         Name A = elementUtils.getName("a");
 330         Name CODE = elementUtils.getName("code");
 331         Name HREF = elementUtils.getName("href");
 332         List<DocTree> code = List.of(
 333                 treeFactory.newStartElementTree(CODE, List.of(), false),
 334                 treeFactory.newTextTree(component.toString()),
 335                 treeFactory.newEndElementTree(CODE));
 336         if (hasParamForComponent(elem, component)) {
 337             DocTree href = treeFactory.newAttributeTree(HREF,
 338                     AttributeTree.ValueKind.DOUBLE,
 339                     List.of(treeFactory.newTextTree("#param-" + component)));
 340             result.add(treeFactory.newStartElementTree(A, List.of(href), false));
 341             result.addAll(code);
 342             result.add(treeFactory.newEndElementTree(A));
 343         } else {
 344             result.addAll(code);
 345         }
 346         result.add(treeFactory.newTextTree(text.substring(index + 3)));
 347         return result;
 348     }
 349 
 350     /**
 351      * Returns whether or not the doc comment for a record contains an {@code @param}}
 352      * for a state component of the record.
 353      * @param elem the record element
 354      * @param component the name of the component
 355      * @return whether or not there is a {@code @param}} for the component
 356      */
 357     private boolean hasParamForComponent(TypeElement elem, Name component) {
 358         DocCommentTree elemComment = utils.getDocCommentTree(elem);
 359         if (elemComment == null) {
 360             return false;
 361         }
 362 
 363         for (DocTree t : elemComment.getBlockTags()) {
 364             if (t instanceof ParamTree && ((ParamTree) t).getName().getName() == component) {
 365                 return true;
 366             }
 367         }
 368 
 369         return false;
 370     }
 371 
 372     /**
 373      * Creates a description that contains the simple name of a program element
 374      * The description is looked up as a resource, and should contain {@code {0}} where the
 375      * name is to be inserted. T
 376      * @param key the resource key for the description
 377      * @param name the name
 378      * @return the description
 379      */
 380     private List<DocTree> makeDescriptionWithName(String key, Name name) {
 381         String text = resources.getText(key);
 382         int index = text.indexOf("{0}");
 383         if (index == -1) {
 384             return List.of(treeFactory.newTextTree(text));
 385         } else {
 386             Name CODE = elementUtils.getName("code");
 387             return List.of(
 388                     treeFactory.newTextTree(text.substring(0, index)),
 389                     treeFactory.newStartElementTree(CODE, List.of(), false),
 390                     treeFactory.newTextTree(name.toString()),
 391                     treeFactory.newEndElementTree(CODE),
 392                     treeFactory.newTextTree(text.substring(index + 3))
 393             );
 394         }
 395     }
 396 
 397     /*
 398      * Returns the TreePath/DocCommentTree duo for synthesized element.
 399      */
 400     public DocCommentDuo getSyntheticCommentDuo(Element e) {
 401         return dcTreesMap.get(e);
 402     }
 403 
 404     /*
 405      * Returns the TreePath/DocCommentTree duo for html sources.
 406      */
 407     public DocCommentDuo getHtmlCommentDuo(Element e) {
 408         FileObject fo = null;
 409         PackageElement pe = null;
 410         switch (e.getKind()) {
 411             case OTHER:
 412                 if (e instanceof DocletElement) {
 413                     DocletElement de = (DocletElement)e;
 414                     fo = de.getFileObject();
 415                     pe = de.getPackageElement();
 416                 }
 417                 break;
 418             case PACKAGE:
 419                 fo = configuration.workArounds.getJavaFileObject((PackageElement)e);
 420                 pe = (PackageElement)e;
 421                 break;
 422             default:
 423                 return null;
 424         }
 425         if (fo == null) {
 426             return null;
 427         }
 428 
 429         DocCommentTree dcTree = trees.getDocCommentTree(fo);
 430         if (dcTree == null) {
 431             return null;
 432         }
 433         DocTreePath treePath = trees.getDocTreePath(fo, pe);
 434         return new DocCommentDuo(treePath.getTreePath(), dcTree);
 435     }
 436 
 437     public DocCommentTree parse(URI uri, String text) {
 438         return trees.getDocCommentTree(new SimpleJavaFileObject(
 439                 uri, JavaFileObject.Kind.SOURCE) {
 440             @Override @DefinedBy(Api.COMPILER)
 441             public CharSequence getCharContent(boolean ignoreEncoding) {
 442                 return text;
 443             }
 444         });
 445     }
 446 
 447     public void setDocCommentTree(Element element, List<? extends DocTree> fullBody,
 448                                   List<? extends DocTree> blockTags) {
 449         DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, blockTags);
 450         dcTreesMap.put(element, new DocCommentDuo(null, docTree));
 451         // A method having null comment (no comment) that might need to be replaced
 452         // with a synthetic comment, remove such a comment from the cache.
 453         utils.removeCommentHelper(element);
 454     }
 455 
 456     /**
 457      * A simplistic container to transport a TreePath, DocCommentTree pair.
 458      * Here is why we need this:
 459      * a. not desirable to add javac's pair.
 460      * b. DocTreePath is not a viable  option either, as a null TreePath is required
 461      * to represent synthetic comments for Enum.values, valuesOf, javafx properties.
 462      */
 463     public static class DocCommentDuo {
 464         public final TreePath treePath;
 465         public final DocCommentTree dcTree;
 466 
 467         public DocCommentDuo(TreePath treePath, DocCommentTree dcTree) {
 468             this.treePath = treePath;
 469             this.dcTree = dcTree;
 470         }
 471     }
 472 }