1 /* 2 * Copyright (c) 2003, 2019, 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 package jdk.javadoc.internal.doclets.formats.html; 27 28 import java.util.List; 29 30 import javax.lang.model.element.Element; 31 import javax.lang.model.element.ElementKind; 32 import javax.lang.model.element.ExecutableElement; 33 import javax.lang.model.element.ModuleElement; 34 import javax.lang.model.element.PackageElement; 35 import javax.lang.model.element.TypeElement; 36 import javax.lang.model.element.VariableElement; 37 import javax.lang.model.type.TypeMirror; 38 import javax.lang.model.util.SimpleElementVisitor14; 39 40 import com.sun.source.doctree.DocTree; 41 import com.sun.source.doctree.IndexTree; 42 import com.sun.source.doctree.ParamTree; 43 import com.sun.source.doctree.SystemPropertyTree; 44 import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; 45 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; 46 import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree; 47 import jdk.javadoc.internal.doclets.formats.html.markup.RawHtml; 48 import jdk.javadoc.internal.doclets.formats.html.markup.StringContent; 49 import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration; 50 import jdk.javadoc.internal.doclets.toolkit.Content; 51 import jdk.javadoc.internal.doclets.toolkit.DocletElement; 52 import jdk.javadoc.internal.doclets.toolkit.Resources; 53 import jdk.javadoc.internal.doclets.toolkit.builders.SerializedFormBuilder; 54 import jdk.javadoc.internal.doclets.toolkit.taglets.TagletWriter; 55 import jdk.javadoc.internal.doclets.toolkit.util.CommentHelper; 56 import jdk.javadoc.internal.doclets.toolkit.util.DocLink; 57 import jdk.javadoc.internal.doclets.toolkit.util.DocPath; 58 import jdk.javadoc.internal.doclets.toolkit.util.DocPaths; 59 import jdk.javadoc.internal.doclets.toolkit.util.DocletConstants; 60 import jdk.javadoc.internal.doclets.toolkit.util.Utils; 61 62 /** 63 * The taglet writer that writes HTML. 64 * 65 * <p><b>This is NOT part of any supported API. 66 * If you write code that depends on this, you do so at your own risk. 67 * This code and its internal interfaces are subject to change or 68 * deletion without notice.</b> 69 */ 70 71 public class TagletWriterImpl extends TagletWriter { 72 73 private final HtmlDocletWriter htmlWriter; 74 private final HtmlConfiguration configuration; 75 private final Utils utils; 76 private final boolean inSummary; 77 private final Resources resources; 78 79 public TagletWriterImpl(HtmlDocletWriter htmlWriter, boolean isFirstSentence) { 80 this(htmlWriter, isFirstSentence, false); 81 } 82 83 public TagletWriterImpl(HtmlDocletWriter htmlWriter, boolean isFirstSentence, boolean inSummary) { 84 super(isFirstSentence); 85 this.htmlWriter = htmlWriter; 86 configuration = htmlWriter.configuration; 87 this.utils = configuration.utils; 88 this.inSummary = inSummary; 89 resources = configuration.getResources(); 90 } 91 92 /** 93 * {@inheritDoc} 94 */ 95 public Content getOutputInstance() { 96 return new ContentBuilder(); 97 } 98 99 /** 100 * {@inheritDoc} 101 */ 102 protected Content codeTagOutput(Element element, DocTree tag) { 103 CommentHelper ch = utils.getCommentHelper(element); 104 StringContent content = new StringContent(utils.normalizeNewlines(ch.getText(tag))); 105 Content result = HtmlTree.CODE(content); 106 return result; 107 } 108 109 protected Content indexTagOutput(Element element, DocTree tag) { 110 CommentHelper ch = utils.getCommentHelper(element); 111 IndexTree itt = (IndexTree)tag; 112 113 String tagText = ch.getText(itt.getSearchTerm()); 114 if (tagText.charAt(0) == '"' && tagText.charAt(tagText.length() - 1) == '"') { 115 tagText = tagText.substring(1, tagText.length() - 1) 116 .replaceAll("\\s+", " "); 117 } 118 String desc = ch.getText(itt.getDescription()); 119 120 return createAnchorAndSearchIndex(element, tagText, desc, false); 121 } 122 123 /** 124 * {@inheritDoc} 125 */ 126 public Content getDocRootOutput() { 127 String path; 128 if (htmlWriter.pathToRoot.isEmpty()) 129 path = "."; 130 else 131 path = htmlWriter.pathToRoot.getPath(); 132 return new StringContent(path); 133 } 134 135 /** 136 * {@inheritDoc} 137 */ 138 public Content deprecatedTagOutput(Element element) { 139 ContentBuilder result = new ContentBuilder(); 140 CommentHelper ch = utils.getCommentHelper(element); 141 List<? extends DocTree> deprs = utils.getBlockTags(element, DocTree.Kind.DEPRECATED); 142 if (utils.isTypeElement(element)) { 143 if (utils.isDeprecated(element)) { 144 result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, 145 htmlWriter.getDeprecatedPhrase(element))); 146 if (!deprs.isEmpty()) { 147 List<? extends DocTree> commentTags = ch.getDescription(configuration, deprs.get(0)); 148 if (!commentTags.isEmpty()) { 149 result.add(commentTagsToOutput(null, element, commentTags, false)); 150 } 151 } 152 } 153 } else { 154 if (utils.isDeprecated(element)) { 155 result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, 156 htmlWriter.getDeprecatedPhrase(element))); 157 if (!deprs.isEmpty()) { 158 List<? extends DocTree> bodyTags = ch.getBody(configuration, deprs.get(0)); 159 Content body = commentTagsToOutput(null, element, bodyTags, false); 160 if (!body.isEmpty()) 161 result.add(HtmlTree.DIV(HtmlStyle.deprecationComment, body)); 162 } 163 } else { 164 Element ee = utils.getEnclosingTypeElement(element); 165 if (utils.isDeprecated(ee)) { 166 result.add(HtmlTree.SPAN(HtmlStyle.deprecatedLabel, 167 htmlWriter.getDeprecatedPhrase(ee))); 168 } 169 } 170 } 171 return result; 172 } 173 174 /** 175 * {@inheritDoc} 176 */ 177 protected Content literalTagOutput(Element element, DocTree tag) { 178 CommentHelper ch = utils.getCommentHelper(element); 179 Content result = new StringContent(utils.normalizeNewlines(ch.getText(tag))); 180 return result; 181 } 182 183 /** 184 * {@inheritDoc} 185 */ 186 public Content getParamHeader(String header) { 187 HtmlTree result = HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.paramLabel, 188 new StringContent(header))); 189 return result; 190 } 191 192 /** 193 * {@inheritDoc} 194 */ 195 @SuppressWarnings("preview") 196 public Content paramTagOutput(Element element, DocTree paramTag, String paramName) { 197 ContentBuilder body = new ContentBuilder(); 198 CommentHelper ch = utils.getCommentHelper(element); 199 // define id attributes for state components so that generated descriptions may refer to them 200 boolean defineID = (element.getKind() == ElementKind.RECORD) 201 && (paramTag instanceof ParamTree) && !((ParamTree) paramTag).isTypeParameter(); 202 Content nameTree = new StringContent(paramName); 203 body.add(HtmlTree.CODE(defineID ? HtmlTree.SPAN_ID("param-" + paramName, nameTree) : nameTree)); 204 body.add(" - "); 205 List<? extends DocTree> description = ch.getDescription(configuration, paramTag); 206 body.add(htmlWriter.commentTagsToContent(paramTag, element, description, false, inSummary)); 207 return HtmlTree.DD(body); 208 } 209 210 /** 211 * {@inheritDoc} 212 */ 213 public Content propertyTagOutput(Element element, DocTree tag, String prefix) { 214 Content body = new ContentBuilder(); 215 CommentHelper ch = utils.getCommentHelper(element); 216 body.add(new RawHtml(prefix)); 217 body.add(" "); 218 body.add(HtmlTree.CODE(new RawHtml(ch.getText(tag)))); 219 body.add("."); 220 Content result = HtmlTree.P(body); 221 return result; 222 } 223 224 /** 225 * {@inheritDoc} 226 */ 227 public Content returnTagOutput(Element element, DocTree returnTag) { 228 ContentBuilder result = new ContentBuilder(); 229 CommentHelper ch = utils.getCommentHelper(element); 230 result.add(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.returnLabel, 231 new StringContent(resources.getText("doclet.Returns"))))); 232 result.add(HtmlTree.DD(htmlWriter.commentTagsToContent( 233 returnTag, element, ch.getDescription(configuration, returnTag), false, inSummary))); 234 return result; 235 } 236 237 /** 238 * {@inheritDoc} 239 */ 240 public Content seeTagOutput(Element holder, List<? extends DocTree> seeTags) { 241 ContentBuilder body = new ContentBuilder(); 242 for (DocTree dt : seeTags) { 243 appendSeparatorIfNotEmpty(body); 244 body.add(htmlWriter.seeTagToContent(holder, dt)); 245 } 246 if (utils.isVariableElement(holder) && ((VariableElement)holder).getConstantValue() != null && 247 htmlWriter instanceof ClassWriterImpl) { 248 //Automatically add link to constant values page for constant fields. 249 appendSeparatorIfNotEmpty(body); 250 DocPath constantsPath = 251 htmlWriter.pathToRoot.resolve(DocPaths.CONSTANT_VALUES); 252 String whichConstant = 253 ((ClassWriterImpl) htmlWriter).getTypeElement().getQualifiedName() + "." + 254 utils.getSimpleName(holder); 255 DocLink link = constantsPath.fragment(whichConstant); 256 body.add(htmlWriter.links.createLink(link, 257 new StringContent(resources.getText("doclet.Constants_Summary")))); 258 } 259 if (utils.isClass(holder) && utils.isSerializable((TypeElement)holder)) { 260 //Automatically add link to serialized form page for serializable classes. 261 if (SerializedFormBuilder.serialInclude(utils, holder) && 262 SerializedFormBuilder.serialInclude(utils, utils.containingPackage(holder))) { 263 appendSeparatorIfNotEmpty(body); 264 DocPath serialPath = htmlWriter.pathToRoot.resolve(DocPaths.SERIALIZED_FORM); 265 DocLink link = serialPath.fragment(utils.getFullyQualifiedName(holder)); 266 body.add(htmlWriter.links.createLink(link, 267 new StringContent(resources.getText("doclet.Serialized_Form")))); 268 } 269 } 270 if (body.isEmpty()) 271 return body; 272 273 ContentBuilder result = new ContentBuilder(); 274 result.add(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.seeLabel, 275 new StringContent(resources.getText("doclet.See_Also"))))); 276 result.add(HtmlTree.DD(body)); 277 return result; 278 279 } 280 281 private void appendSeparatorIfNotEmpty(ContentBuilder body) { 282 if (!body.isEmpty()) { 283 body.add(", "); 284 body.add(DocletConstants.NL); 285 } 286 } 287 288 /** 289 * {@inheritDoc} 290 */ 291 public Content simpleTagOutput(Element element, List<? extends DocTree> simpleTags, String header) { 292 CommentHelper ch = utils.getCommentHelper(element); 293 ContentBuilder result = new ContentBuilder(); 294 result.add(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.simpleTagLabel, new RawHtml(header)))); 295 ContentBuilder body = new ContentBuilder(); 296 boolean many = false; 297 for (DocTree simpleTag : simpleTags) { 298 if (many) { 299 body.add(", "); 300 } 301 List<? extends DocTree> bodyTags = ch.getBody(configuration, simpleTag); 302 body.add(htmlWriter.commentTagsToContent(simpleTag, element, bodyTags, false, inSummary)); 303 many = true; 304 } 305 result.add(HtmlTree.DD(body)); 306 return result; 307 } 308 309 /** 310 * {@inheritDoc} 311 */ 312 public Content simpleTagOutput(Element element, DocTree simpleTag, String header) { 313 ContentBuilder result = new ContentBuilder(); 314 result.add(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.simpleTagLabel, new RawHtml(header)))); 315 CommentHelper ch = utils.getCommentHelper(element); 316 List<? extends DocTree> description = ch.getDescription(configuration, simpleTag); 317 Content body = htmlWriter.commentTagsToContent(simpleTag, element, description, false, inSummary); 318 result.add(HtmlTree.DD(body)); 319 return result; 320 } 321 322 /** 323 * {@inheritDoc} 324 */ 325 protected Content systemPropertyTagOutput(Element element, DocTree tag) { 326 SystemPropertyTree itt = (SystemPropertyTree)tag; 327 String tagText = itt.getPropertyName().toString(); 328 return HtmlTree.CODE(createAnchorAndSearchIndex(element, tagText, 329 resources.getText("doclet.System_Property"), true)); 330 } 331 332 /** 333 * {@inheritDoc} 334 */ 335 public Content getThrowsHeader() { 336 HtmlTree result = HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.throwsLabel, 337 new StringContent(resources.getText("doclet.Throws")))); 338 return result; 339 } 340 341 /** 342 * {@inheritDoc} 343 */ 344 public Content throwsTagOutput(Element element, DocTree throwsTag) { 345 ContentBuilder body = new ContentBuilder(); 346 CommentHelper ch = utils.getCommentHelper(element); 347 Element exception = ch.getException(configuration, throwsTag); 348 Content excName; 349 if (exception == null) { 350 excName = new RawHtml(ch.getExceptionName(throwsTag).toString()); 351 } else if (exception.asType() == null) { 352 excName = new RawHtml(utils.getFullyQualifiedName(exception)); 353 } else { 354 LinkInfoImpl link = new LinkInfoImpl(configuration, LinkInfoImpl.Kind.MEMBER, 355 exception.asType()); 356 link.excludeTypeBounds = true; 357 excName = htmlWriter.getLink(link); 358 } 359 body.add(HtmlTree.CODE(excName)); 360 List<? extends DocTree> description = ch.getDescription(configuration, throwsTag); 361 Content desc = htmlWriter.commentTagsToContent(throwsTag, element, description, false, inSummary); 362 if (desc != null && !desc.isEmpty()) { 363 body.add(" - "); 364 body.add(desc); 365 } 366 HtmlTree result = HtmlTree.DD(body); 367 return result; 368 } 369 370 /** 371 * {@inheritDoc} 372 */ 373 public Content throwsTagOutput(TypeMirror throwsType) { 374 HtmlTree result = HtmlTree.DD(HtmlTree.CODE(htmlWriter.getLink( 375 new LinkInfoImpl(configuration, LinkInfoImpl.Kind.MEMBER, throwsType)))); 376 return result; 377 } 378 379 /** 380 * {@inheritDoc} 381 */ 382 public Content valueTagOutput(VariableElement field, String constantVal, boolean includeLink) { 383 return includeLink 384 ? htmlWriter.getDocLink(LinkInfoImpl.Kind.VALUE_TAG, field, constantVal, false) 385 : new StringContent(constantVal); 386 } 387 388 /** 389 * {@inheritDoc} 390 */ 391 public Content commentTagsToOutput(DocTree holderTag, List<? extends DocTree> tags) { 392 return commentTagsToOutput(holderTag, null, tags, false); 393 } 394 395 /** 396 * {@inheritDoc} 397 */ 398 public Content commentTagsToOutput(Element holder, List<? extends DocTree> tags) { 399 return commentTagsToOutput(null, holder, tags, false); 400 } 401 402 /** 403 * {@inheritDoc} 404 */ 405 public Content commentTagsToOutput(DocTree holderTag, 406 Element holder, List<? extends DocTree> tags, boolean isFirstSentence) { 407 return htmlWriter.commentTagsToContent(holderTag, holder, 408 tags, isFirstSentence, inSummary); 409 } 410 411 /** 412 * {@inheritDoc} 413 */ 414 public BaseConfiguration configuration() { 415 return configuration; 416 } 417 418 @SuppressWarnings("preview") 419 private Content createAnchorAndSearchIndex(Element element, String tagText, String desc, boolean isSystemProperty) { 420 Content result = null; 421 if (isFirstSentence && inSummary) { 422 result = new StringContent(tagText); 423 } else { 424 String anchorName = htmlWriter.links.getName(tagText); 425 int count = htmlWriter.indexAnchorTable 426 .compute(anchorName, (k, v) -> v == null ? 0 : v + 1); 427 if (count > 0) { 428 anchorName += "-" + count; 429 } 430 result = HtmlTree.SPAN(anchorName, HtmlStyle.searchTagResult, new StringContent(tagText)); 431 if (configuration.createindex && !tagText.isEmpty()) { 432 SearchIndexItem si = new SearchIndexItem(); 433 si.setSystemProperty(isSystemProperty); 434 si.setLabel(tagText); 435 si.setDescription(desc); 436 si.setUrl(htmlWriter.path.getPath() + "#" + anchorName); 437 new SimpleElementVisitor14<Void, Void>() { 438 439 @Override 440 public Void visitModule(ModuleElement e, Void p) { 441 si.setHolder(resources.getText("doclet.module") 442 + " " + utils.getFullyQualifiedName(e)); 443 return null; 444 } 445 446 @Override 447 public Void visitPackage(PackageElement e, Void p) { 448 si.setHolder(resources.getText("doclet.package") 449 + " " + utils.getFullyQualifiedName(e)); 450 return null; 451 } 452 453 @Override 454 public Void visitType(TypeElement e, Void p) { 455 si.setHolder(utils.getTypeElementName(e, true) 456 + " " + utils.getFullyQualifiedName(e)); 457 return null; 458 } 459 460 @Override 461 public Void visitExecutable(ExecutableElement e, Void p) { 462 si.setHolder(utils.getFullyQualifiedName(utils.getEnclosingTypeElement(e)) 463 + "." + utils.getSimpleName(e) + utils.flatSignature(e)); 464 return null; 465 } 466 467 @Override 468 public Void visitVariable(VariableElement e, Void p) { 469 TypeElement te = utils.getEnclosingTypeElement(e); 470 si.setHolder(utils.getFullyQualifiedName(te) + "." + utils.getSimpleName(e)); 471 return null; 472 } 473 474 @Override 475 public Void visitUnknown(Element e, Void p) { 476 if (e instanceof DocletElement) { 477 DocletElement de = (DocletElement) e; 478 switch (de.getSubKind()) { 479 case OVERVIEW: 480 si.setHolder(resources.getText("doclet.Overview")); 481 break; 482 case DOCFILE: 483 si.setHolder(getHolderName(de)); 484 break; 485 default: 486 throw new IllegalStateException(); 487 } 488 return null; 489 } else { 490 return super.visitUnknown(e, p); 491 } 492 } 493 494 @Override 495 protected Void defaultAction(Element e, Void p) { 496 si.setHolder(utils.getFullyQualifiedName(e)); 497 return null; 498 } 499 }.visit(element); 500 si.setCategory(SearchIndexItem.Category.SEARCH_TAGS); 501 configuration.tagSearchIndex.add(si); 502 } 503 } 504 return result; 505 } 506 507 private String getHolderName(DocletElement de) { 508 PackageElement pe = de.getPackageElement(); 509 if (pe.isUnnamed()) { 510 // if package is unnamed use enclosing module only if it is named 511 Element ee = pe.getEnclosingElement(); 512 if (ee instanceof ModuleElement && !((ModuleElement)ee).isUnnamed()) { 513 return resources.getText("doclet.module") + " " + utils.getFullyQualifiedName(ee); 514 } 515 return pe.toString(); // "Unnamed package" or similar 516 } 517 return resources.getText("doclet.package") + " " + utils.getFullyQualifiedName(pe); 518 } 519 }