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