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 }