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