001/*
002 * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package org.jdrupes.mdoclet.internal.doclets.formats.html;
027
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.List;
031import java.util.Set;
032import java.util.SortedSet;
033import java.util.TreeSet;
034import javax.lang.model.element.AnnotationMirror;
035import javax.lang.model.element.Element;
036import javax.lang.model.element.ModuleElement;
037import javax.lang.model.element.PackageElement;
038import javax.lang.model.element.TypeElement;
039import javax.lang.model.type.TypeMirror;
040import javax.lang.model.util.SimpleElementVisitor8;
041
042import org.jdrupes.mdoclet.internal.doclets.formats.html.Navigation.PageMode;
043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder;
044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Entity;
045import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlAttr;
046import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle;
047import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree;
048import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName;
049import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Text;
050import org.jdrupes.mdoclet.internal.doclets.toolkit.ClassWriter;
051import org.jdrupes.mdoclet.internal.doclets.toolkit.Content;
052import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.ParamTaglet;
053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.ClassTree;
054import org.jdrupes.mdoclet.internal.doclets.toolkit.util.CommentHelper;
055import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException;
056import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath;
057import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable;
058
059import com.sun.source.doctree.DeprecatedTree;
060import com.sun.source.doctree.DocTree;
061
062/**
063 * Generate the Class Information Page.
064 *
065 * @see javax.lang.model.element.TypeElement
066 */
067public class ClassWriterImpl extends SubWriterHolderWriter
068        implements ClassWriter {
069
070    private static final Set<String> suppressSubtypesSet
071        = Set.of("java.lang.Object",
072            "org.omg.CORBA.Object");
073
074    private static final Set<String> suppressImplementingSet
075        = Set.of("java.lang.Cloneable",
076            "java.lang.constant.Constable",
077            "java.lang.constant.ConstantDesc",
078            "java.io.Serializable");
079
080    protected final TypeElement typeElement;
081
082    protected final ClassTree classTree;
083
084    /**
085     * @param configuration the configuration data for the doclet
086     * @param typeElement the class being documented.
087     * @param classTree the class tree for the given class.
088     */
089    public ClassWriterImpl(HtmlConfiguration configuration,
090            TypeElement typeElement,
091            ClassTree classTree) {
092        super(configuration, configuration.docPaths.forClass(typeElement));
093        this.typeElement = typeElement;
094        configuration.currentTypeElement = typeElement;
095        this.classTree = classTree;
096    }
097
098    @Override
099    public Content getHeader(String header) {
100        HtmlTree body
101            = getBody(getWindowTitle(utils.getSimpleName(typeElement)));
102        var div = HtmlTree.DIV(HtmlStyle.header);
103        if (configuration.showModules) {
104            ModuleElement mdle = configuration.docEnv.getElementUtils()
105                .getModuleOf(typeElement);
106            var classModuleLabel = HtmlTree.SPAN(HtmlStyle.moduleLabelInType,
107                contents.moduleLabel);
108            var moduleNameDiv
109                = HtmlTree.DIV(HtmlStyle.subTitle, classModuleLabel);
110            moduleNameDiv.add(Entity.NO_BREAK_SPACE);
111            moduleNameDiv.add(getModuleLink(mdle,
112                Text.of(mdle.getQualifiedName())));
113            div.add(moduleNameDiv);
114        }
115        PackageElement pkg = utils.containingPackage(typeElement);
116        if (!pkg.isUnnamed()) {
117            var classPackageLabel = HtmlTree.SPAN(HtmlStyle.packageLabelInType,
118                contents.packageLabel);
119            var pkgNameDiv
120                = HtmlTree.DIV(HtmlStyle.subTitle, classPackageLabel);
121            pkgNameDiv.add(Entity.NO_BREAK_SPACE);
122            Content pkgNameContent
123                = getPackageLink(pkg, getLocalizedPackageName(pkg));
124            pkgNameDiv.add(pkgNameContent);
125            div.add(pkgNameDiv);
126        }
127        HtmlLinkInfo linkInfo = new HtmlLinkInfo(configuration,
128            HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_AND_BOUNDS, typeElement)
129                .linkToSelf(false);  // Let's not link to ourselves in the
130                                     // header
131        var heading = HtmlTree.HEADING_TITLE(Headings.PAGE_TITLE_HEADING,
132            HtmlStyle.title, Text.of(header));
133        heading.add(getTypeParameterLinks(linkInfo));
134        div.add(heading);
135        bodyContents.setHeader(getHeader(PageMode.CLASS, typeElement))
136            .addMainContent(MarkerComments.START_OF_CLASS_DATA)
137            .addMainContent(div);
138        return body;
139    }
140
141    @Override
142    public Content getClassContentHeader() {
143        return getContentHeader();
144    }
145
146    @Override
147    protected Navigation getNavBar(PageMode pageMode, Element element) {
148        Content linkContent
149            = getModuleLink(utils.elementUtils.getModuleOf(element),
150                contents.moduleLabel);
151        return super.getNavBar(pageMode, element)
152            .setNavLinkModule(linkContent)
153            .setSubNavLinks(() -> {
154                List<Content> list = new ArrayList<>();
155                VisibleMemberTable vmt
156                    = configuration.getVisibleMemberTable(typeElement);
157                Set<VisibleMemberTable.Kind> summarySet
158                    = VisibleMemberTable.Kind.forSummariesOf(element.getKind());
159                for (VisibleMemberTable.Kind kind : summarySet) {
160                    list.add(links.createLink(HtmlIds.forMemberSummary(kind),
161                        contents.getNavLinkLabelContent(kind),
162                        vmt.hasVisibleMembers(kind)));
163                }
164                return list;
165            });
166    }
167
168    @Override
169    public void addFooter() {
170        bodyContents.addMainContent(MarkerComments.END_OF_CLASS_DATA);
171        bodyContents.setFooter(getFooter());
172    }
173
174    @Override
175    public void printDocument(Content content) throws DocFileIOException {
176        String description = getDescription("declaration", typeElement);
177        PackageElement pkg = utils.containingPackage(typeElement);
178        List<DocPath> localStylesheets = getLocalStylesheets(pkg);
179        content.add(bodyContents);
180        printHtmlDocument(
181            configuration.metakeywords.getMetaKeywords(typeElement),
182            description, localStylesheets, content);
183    }
184
185    @Override
186    public Content getClassInfo(Content classInfo) {
187        return getMember(HtmlIds.CLASS_DESCRIPTION, HtmlStyle.classDescription,
188            classInfo);
189    }
190
191    @Override
192    protected TypeElement getCurrentPageElement() {
193        return typeElement;
194    }
195
196    @Override
197    public void addClassSignature(Content classInfo) {
198        classInfo.add(new HtmlTree(TagName.HR));
199        classInfo.add(new Signatures.TypeSignature(typeElement, this)
200            .toContent());
201    }
202
203    @Override
204    public void addClassDescription(Content classInfo) {
205        addPreviewInfo(classInfo);
206        if (!options.noComment()) {
207            // generate documentation for the class.
208            if (!utils.getFullBody(typeElement).isEmpty()) {
209                addInlineComment(typeElement, classInfo);
210            }
211        }
212    }
213
214    private void addPreviewInfo(Content content) {
215        addPreviewInfo(typeElement, content);
216    }
217
218    @Override
219    public void addClassTagInfo(Content classInfo) {
220        if (!options.noComment()) {
221            // Print Information about all the tags here
222            addTagsInfo(typeElement, classInfo);
223        }
224    }
225
226    /**
227     * Get the class inheritance tree for the given class.
228     *
229     * @param type the class to get the inheritance tree for
230     * @return the class inheritance tree
231     */
232    private Content getClassInheritanceTreeContent(TypeMirror type) {
233        TypeMirror sup;
234        HtmlTree classTree = null;
235        do {
236            sup = utils.getFirstVisibleSuperClass(type);
237            var entry = HtmlTree.DIV(HtmlStyle.inheritance,
238                getClassHelperContent(type));
239            if (classTree != null)
240                entry.add(classTree);
241            classTree = entry;
242            type = sup;
243        } while (sup != null);
244        classTree.put(HtmlAttr.TITLE,
245            contents.getContent("doclet.Inheritance_Tree").toString());
246        return classTree;
247    }
248
249    /**
250     * Get the class helper for the given class.
251     *
252     * @param type the class to get the helper for
253     * @return the class helper
254     */
255    private Content getClassHelperContent(TypeMirror type) {
256        Content result = new ContentBuilder();
257        if (utils.typeUtils.isSameType(type, typeElement.asType())) {
258            Content typeParameters = getTypeParameterLinks(
259                new HtmlLinkInfo(configuration,
260                    HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS,
261                    typeElement));
262            if (configuration.shouldExcludeQualifier(
263                utils.containingPackage(typeElement).toString())) {
264                result.add(utils.asTypeElement(type).getSimpleName());
265                result.add(typeParameters);
266            } else {
267                result.add(utils.asTypeElement(type).getQualifiedName());
268                result.add(typeParameters);
269            }
270        } else {
271            Content link = getLink(new HtmlLinkInfo(configuration,
272                HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS, type)
273                    .label(
274                        configuration.getClassName(utils.asTypeElement(type))));
275            result.add(link);
276        }
277        return result;
278    }
279
280    @Override
281    public void addClassTree(Content target) {
282        if (!utils.isClass(typeElement)) {
283            return;
284        }
285        target.add(getClassInheritanceTreeContent(typeElement.asType()));
286    }
287
288    @Override
289    public void addParamInfo(Content target) {
290        if (utils.hasBlockTag(typeElement, DocTree.Kind.PARAM)) {
291            Content paramInfo
292                = (new ParamTaglet()).getAllBlockTagOutput(typeElement,
293                    getTagletWriterInstance(false));
294            if (!paramInfo.isEmpty()) {
295                target.add(HtmlTree.DL(HtmlStyle.notes, paramInfo));
296            }
297        }
298    }
299
300    @Override
301    public void addSubClassInfo(Content target) {
302        if (utils.isClass(typeElement)) {
303            for (String s : suppressSubtypesSet) {
304                if (typeElement.getQualifiedName().contentEquals(s)) {
305                    return;    // Don't generate the list, too huge
306                }
307            }
308            Set<TypeElement> subclasses
309                = classTree.hierarchy(typeElement).subtypes(typeElement);
310            if (!subclasses.isEmpty()) {
311                var dl = HtmlTree.DL(HtmlStyle.notes);
312                dl.add(HtmlTree.DT(contents.subclassesLabel));
313                dl.add(HtmlTree
314                    .DD(getClassLinks(HtmlLinkInfo.Kind.PLAIN, subclasses)));
315                target.add(dl);
316            }
317        }
318    }
319
320    @Override
321    public void addSubInterfacesInfo(Content target) {
322        if (utils.isPlainInterface(typeElement)) {
323            Set<TypeElement> subInterfaces
324                = classTree.hierarchy(typeElement).allSubtypes(typeElement);
325            if (!subInterfaces.isEmpty()) {
326                var dl = HtmlTree.DL(HtmlStyle.notes);
327                dl.add(HtmlTree.DT(contents.subinterfacesLabel));
328                dl.add(HtmlTree.DD(getClassLinks(
329                    HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS, subInterfaces)));
330                target.add(dl);
331            }
332        }
333    }
334
335    @Override
336    public void addInterfaceUsageInfo(Content target) {
337        if (!utils.isPlainInterface(typeElement)) {
338            return;
339        }
340        for (String s : suppressImplementingSet) {
341            if (typeElement.getQualifiedName().contentEquals(s)) {
342                return;    // Don't generate the list, too huge
343            }
344        }
345        Set<TypeElement> implcl = classTree.implementingClasses(typeElement);
346        if (!implcl.isEmpty()) {
347            var dl = HtmlTree.DL(HtmlStyle.notes);
348            dl.add(HtmlTree.DT(contents.implementingClassesLabel));
349            dl.add(HtmlTree.DD(getClassLinks(HtmlLinkInfo.Kind.PLAIN, implcl)));
350            target.add(dl);
351        }
352    }
353
354    @Override
355    public void addImplementedInterfacesInfo(Content target) {
356        SortedSet<TypeMirror> interfaces
357            = new TreeSet<>(comparators.makeTypeMirrorClassUseComparator());
358        interfaces.addAll(utils.getAllInterfaces(typeElement));
359        if (utils.isClass(typeElement) && !interfaces.isEmpty()) {
360            var dl = HtmlTree.DL(HtmlStyle.notes);
361            dl.add(HtmlTree.DT(contents.allImplementedInterfacesLabel));
362            dl.add(HtmlTree.DD(
363                getClassLinks(HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS, interfaces)));
364            target.add(dl);
365        }
366    }
367
368    @Override
369    public void addSuperInterfacesInfo(Content target) {
370        SortedSet<TypeMirror> interfaces
371            = new TreeSet<>(comparators.makeTypeMirrorIndexUseComparator());
372        interfaces.addAll(utils.getAllInterfaces(typeElement));
373
374        if (utils.isPlainInterface(typeElement) && !interfaces.isEmpty()) {
375            var dl = HtmlTree.DL(HtmlStyle.notes);
376            dl.add(HtmlTree.DT(contents.allSuperinterfacesLabel));
377            dl.add(HtmlTree.DD(
378                getClassLinks(HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS, interfaces)));
379            target.add(dl);
380        }
381    }
382
383    @Override
384    public void addNestedClassInfo(final Content target) {
385        Element outerClass = typeElement.getEnclosingElement();
386        if (outerClass == null)
387            return;
388        new SimpleElementVisitor8<Void, Void>() {
389            @Override
390            public Void visitType(TypeElement e, Void p) {
391                var dl = HtmlTree.DL(HtmlStyle.notes);
392                dl.add(HtmlTree.DT(utils.isPlainInterface(e)
393                    ? contents.enclosingInterfaceLabel
394                    : contents.enclosingClassLabel));
395                dl.add(HtmlTree.DD(
396                    getClassLinks(HtmlLinkInfo.Kind.LINK_TYPE_PARAMS_AND_BOUNDS,
397                        List.of(e))));
398                target.add(dl);
399                return null;
400            }
401        }.visit(outerClass);
402    }
403
404    @Override
405    public void addFunctionalInterfaceInfo(Content target) {
406        if (isFunctionalInterface()) {
407            var dl = HtmlTree.DL(HtmlStyle.notes);
408            dl.add(HtmlTree.DT(contents.functionalInterface));
409            var dd = new HtmlTree(TagName.DD);
410            dd.add(contents.functionalInterfaceMessage);
411            dl.add(dd);
412            target.add(dl);
413        }
414    }
415
416    public boolean isFunctionalInterface() {
417        List<? extends AnnotationMirror> annotationMirrors
418            = typeElement.getAnnotationMirrors();
419        for (AnnotationMirror anno : annotationMirrors) {
420            if (utils.isFunctionalInterface(anno)) {
421                return true;
422            }
423        }
424        return false;
425    }
426
427    @Override
428    public void addClassDeprecationInfo(Content classInfo) {
429        List<? extends DeprecatedTree> deprs
430            = utils.getDeprecatedTrees(typeElement);
431        if (utils.isDeprecated(typeElement)) {
432            var deprLabel = HtmlTree.SPAN(HtmlStyle.deprecatedLabel,
433                getDeprecatedPhrase(typeElement));
434            var div = HtmlTree.DIV(HtmlStyle.deprecationBlock, deprLabel);
435            if (!deprs.isEmpty()) {
436                CommentHelper ch = utils.getCommentHelper(typeElement);
437                DocTree dt = deprs.get(0);
438                List<? extends DocTree> commentTags = ch.getBody(dt);
439                if (!commentTags.isEmpty()) {
440                    addInlineDeprecatedComment(typeElement, deprs.get(0), div);
441                }
442            }
443            classInfo.add(div);
444        }
445    }
446
447    /**
448     * Get the links to the given classes.
449     *
450     * @param context the id of the context where the links will be added
451     * @param list the classes
452     * @return the links
453     */
454    private Content getClassLinks(HtmlLinkInfo.Kind context,
455            Collection<?> list) {
456        Content content = new ContentBuilder();
457        boolean isFirst = true;
458        for (Object type : list) {
459            if (!isFirst) {
460                content.add(Text.of(", "));
461            } else {
462                isFirst = false;
463            }
464            // TODO: should we simply split this method up to avoid instanceof ?
465            if (type instanceof TypeElement te) {
466                Content link = getLink(
467                    new HtmlLinkInfo(configuration, context, te));
468                content.add(HtmlTree.CODE(link));
469            } else {
470                Content link = getLink(
471                    new HtmlLinkInfo(configuration, context,
472                        ((TypeMirror) type)));
473                content.add(HtmlTree.CODE(link));
474            }
475        }
476        return content;
477    }
478
479    /**
480     * Return the TypeElement being documented.
481     *
482     * @return the TypeElement being documented.
483     */
484    @Override
485    public TypeElement getTypeElement() {
486        return typeElement;
487    }
488
489    @Override
490    public Content getMemberDetails(Content content) {
491        var section = HtmlTree.SECTION(HtmlStyle.details, content);
492        // The following id is required by the Navigation bar
493        if (utils.isAnnotationInterface(typeElement)) {
494            section.setId(HtmlIds.ANNOTATION_TYPE_ELEMENT_DETAIL);
495        }
496        return section;
497    }
498}