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