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