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.List;
030
031import javax.lang.model.element.Element;
032import javax.lang.model.element.ExecutableElement;
033import javax.lang.model.element.TypeElement;
034import javax.lang.model.element.TypeParameterElement;
035import javax.lang.model.type.TypeMirror;
036
037import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder;
038import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Entity;
039import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle;
040import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree;
041import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Links;
042import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName;
043import org.jdrupes.mdoclet.internal.doclets.toolkit.Content;
044import org.jdrupes.mdoclet.internal.doclets.toolkit.MemberSummaryWriter;
045import org.jdrupes.mdoclet.internal.doclets.toolkit.MemberWriter;
046import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources;
047import org.jdrupes.mdoclet.internal.doclets.toolkit.taglets.DeprecatedTaglet;
048import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils;
049
050import com.sun.source.doctree.DocTree;
051
052/**
053 * The base class for member writers.
054 */
055public abstract class AbstractMemberWriter
056        implements MemberSummaryWriter, MemberWriter {
057
058    protected final HtmlConfiguration configuration;
059    protected final HtmlOptions options;
060    protected final Utils utils;
061    protected final SubWriterHolderWriter writer;
062    protected final Contents contents;
063    protected final Resources resources;
064    protected final Links links;
065    protected final HtmlIds htmlIds;
066
067    protected final TypeElement typeElement;
068
069    public AbstractMemberWriter(SubWriterHolderWriter writer,
070            TypeElement typeElement) {
071        this.configuration = writer.configuration;
072        this.options = configuration.getOptions();
073        this.writer = writer;
074        this.typeElement = typeElement;
075        this.utils = configuration.utils;
076        this.contents = configuration.getContents();
077        this.resources = configuration.docResources;
078        this.links = writer.links;
079        this.htmlIds = configuration.htmlIds;
080    }
081
082    public AbstractMemberWriter(SubWriterHolderWriter writer) {
083        this(writer, null);
084    }
085
086    /* ----- abstracts ----- */
087
088    /**
089     * Adds the summary label for the member.
090     *
091     * @param content the content to which the label will be added
092     */
093    public abstract void addSummaryLabel(Content content);
094
095    /**
096     * Returns the summary table header for the member.
097     *
098     * @param member the member to be documented
099     *
100     * @return the summary table header
101     */
102    public abstract TableHeader getSummaryTableHeader(Element member);
103
104    private Table<Element> summaryTable;
105
106    private Table<Element> getSummaryTable() {
107        if (summaryTable == null) {
108            summaryTable = createSummaryTable();
109        }
110        return summaryTable;
111    }
112
113    /**
114     * Creates the summary table for this element.
115     * The table should be created and initialized if needed, and configured
116     * so that it is ready to add content with {@link Table#addRow(Content[])}
117     * and similar methods.
118     *
119     * @return the summary table
120     */
121    protected abstract Table<Element> createSummaryTable();
122
123    /**
124     * Adds inherited summary label for the member.
125     *
126     * @param typeElement the type element to which to link to
127     * @param content     the content to which the inherited summary label will be added
128     */
129    public abstract void addInheritedSummaryLabel(TypeElement typeElement,
130            Content content);
131
132    /**
133     * Adds the summary type for the member.
134     *
135     * @param member  the member to be documented
136     * @param content the content to which the type will be added
137     */
138    protected abstract void addSummaryType(Element member, Content content);
139
140    /**
141     * Adds the summary link for the member.
142     *
143     * @param typeElement the type element to be documented
144     * @param member      the member to be documented
145     * @param content     the content to which the link will be added
146     */
147    protected void addSummaryLink(TypeElement typeElement, Element member,
148            Content content) {
149        addSummaryLink(HtmlLinkInfo.Kind.PLAIN, typeElement, member, content);
150    }
151
152    /**
153     * Adds the summary link for the member.
154     *
155     * @param context     the id of the context where the link will be printed
156     * @param typeElement the type element to be documented
157     * @param member      the member to be documented
158     * @param content     the content to which the summary link will be added
159     */
160    protected abstract void addSummaryLink(HtmlLinkInfo.Kind context,
161            TypeElement typeElement, Element member, Content content);
162
163    /**
164     * Adds the inherited summary link for the member.
165     *
166     * @param typeElement the type element to be documented
167     * @param member      the member to be documented
168     * @param target      the content to which the inherited summary link will be added
169     */
170    protected abstract void addInheritedSummaryLink(TypeElement typeElement,
171            Element member, Content target);
172
173    /**
174     * Returns a link for summary (deprecated, preview) pages.
175     *
176     * @param member the member being linked to
177     *
178     * @return the link
179     */
180    protected abstract Content getSummaryLink(Element member);
181
182    /**
183     * Adds the modifiers and type for the member in the member summary.
184     *
185     * @param member the member to add the modifiers and type for
186     * @param type   the type to add
187     * @param target the content to which the modifiers and type will be added
188     */
189    protected void addModifiersAndType(Element member, TypeMirror type,
190            Content target) {
191        var code = new HtmlTree(TagName.CODE);
192        addModifiers(member, code);
193        if (type == null) {
194            code.add(switch (member.getKind()) {
195            case ENUM -> "enum";
196            case INTERFACE -> "interface";
197            case ANNOTATION_TYPE -> "@interface";
198            case RECORD -> "record";
199            default -> "class";
200            });
201            code.add(Entity.NO_BREAK_SPACE);
202        } else {
203            List<? extends TypeParameterElement> list
204                = utils.isExecutableElement(member)
205                    ? ((ExecutableElement) member).getTypeParameters()
206                    : null;
207            if (list != null && !list.isEmpty()) {
208                Content typeParameters = ((AbstractExecutableMemberWriter) this)
209                    .getTypeParameters((ExecutableElement) member);
210                code.add(typeParameters);
211                // Add explicit line break between method type parameters and
212                // return type in member summary table to avoid random wrapping.
213                if (typeParameters.charCount() > 10) {
214                    code.add(new HtmlTree(TagName.BR));
215                } else {
216                    code.add(Entity.NO_BREAK_SPACE);
217                }
218            }
219            code.add(
220                writer.getLink(new HtmlLinkInfo(configuration,
221                    HtmlLinkInfo.Kind.LINK_TYPE_PARAMS, type)
222                        .addLineBreakOpportunitiesInTypeParameters(true)));
223        }
224        target.add(code);
225    }
226
227    /**
228     * Adds the modifiers for the member.
229     *
230     * @param member the member to add the modifiers for
231     * @param target the content to which the modifiers will be added
232     */
233    private void addModifiers(Element member, Content target) {
234        if (utils.isProtected(member)) {
235            target.add("protected ");
236        } else if (utils.isPrivate(member)) {
237            target.add("private ");
238        } else if (!utils.isPublic(member)) { // Package private
239            target.add(resources.getText("doclet.Package_private"));
240            target.add(" ");
241        }
242        if (!utils.isAnnotationInterface(member.getEnclosingElement())
243            && utils.isMethod(member)) {
244            if (!utils.isPlainInterface(member.getEnclosingElement())
245                && utils.isAbstract(member)) {
246                target.add("abstract ");
247            }
248            if (utils.isDefault(member)) {
249                target.add("default ");
250            }
251        }
252        if (utils.isStatic(member)) {
253            target.add("static ");
254        }
255        if (!utils.isEnum(member) && utils.isFinal(member)) {
256            target.add("final ");
257        }
258    }
259
260    /**
261     * Adds the deprecated information for the given member.
262     *
263     * @param member the member being documented.
264     * @param target the content to which the deprecated information will be added.
265     */
266    protected void addDeprecatedInfo(Element member, Content target) {
267        Content output = (new DeprecatedTaglet()).getAllBlockTagOutput(member,
268            writer.getTagletWriterInstance(false));
269        if (!output.isEmpty()) {
270            target.add(HtmlTree.DIV(HtmlStyle.deprecationBlock, output));
271        }
272    }
273
274    /**
275     * Adds the comment for the given member.
276     *
277     * @param member  the member being documented.
278     * @param content the content to which the comment will be added.
279     */
280    protected void addComment(Element member, Content content) {
281        if (!utils.getFullBody(member).isEmpty()) {
282            writer.addInlineComment(member, content);
283        }
284    }
285
286    /**
287     * Add the preview information for the given member.
288     *
289     * @param member the member being documented.
290     * @param content the content to which the preview information will be added.
291     */
292    protected void addPreviewInfo(Element member, Content content) {
293        writer.addPreviewInfo(member, content);
294    }
295
296    protected String name(Element member) {
297        return utils.getSimpleName(member);
298    }
299
300    /**
301     * Adds use information to the documentation.
302     *
303     * @param members list of program elements for which the use information will be added
304     * @param heading the section heading
305     * @param content the content to which the use information will be added
306     */
307    protected void addUseInfo(List<? extends Element> members, Content heading,
308            Content content) {
309        if (members == null || members.isEmpty()) {
310            return;
311        }
312        boolean printedUseTableHeader = false;
313        var useTable = new Table<Void>(HtmlStyle.summaryTable)
314            .setCaption(heading)
315            .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colSecond,
316                HtmlStyle.colLast);
317        for (Element element : members) {
318            TypeElement te = (typeElement == null)
319                ? utils.getEnclosingTypeElement(element)
320                : typeElement;
321            if (!printedUseTableHeader) {
322                useTable.setHeader(getSummaryTableHeader(element));
323                printedUseTableHeader = true;
324            }
325            Content summaryType = new ContentBuilder();
326            addSummaryType(element, summaryType);
327            Content typeContent = new ContentBuilder();
328            if (te != null
329                && !utils.isConstructor(element)
330                && !utils.isTypeElement(element)) {
331
332                var name = HtmlTree.SPAN(HtmlStyle.typeNameLabel);
333                name.add(name(te) + ".");
334                typeContent.add(name);
335            }
336            addSummaryLink(
337                utils.isClass(element) || utils.isPlainInterface(element)
338                    ? HtmlLinkInfo.Kind.SHOW_TYPE_PARAMS_AND_BOUNDS
339                    : HtmlLinkInfo.Kind.PLAIN,
340                te, element, typeContent);
341            Content desc = new ContentBuilder();
342            writer.addSummaryLinkComment(element, desc);
343            useTable.addRow(summaryType, typeContent, desc);
344        }
345        content.add(useTable);
346    }
347
348    protected void serialWarning(Element e, String key, String a1, String a2) {
349        if (options.serialWarn()) {
350            configuration.messages.warning(e, key, a1, a2);
351        }
352    }
353
354    @Override
355    public void addMemberSummary(TypeElement tElement, Element member,
356            List<? extends DocTree> firstSentenceTrees) {
357        if (tElement != typeElement) {
358            throw new IllegalStateException();
359        }
360        var table = getSummaryTable();
361        List<Content> rowContents = new ArrayList<>();
362        Content summaryType = new ContentBuilder();
363        addSummaryType(member, summaryType);
364        if (!summaryType.isEmpty())
365            rowContents.add(summaryType);
366        Content summaryLink = new ContentBuilder();
367        addSummaryLink(tElement, member, summaryLink);
368        rowContents.add(summaryLink);
369        Content desc = new ContentBuilder();
370        writer.addSummaryLinkComment(member, firstSentenceTrees, desc);
371        rowContents.add(desc);
372        table.addRow(member, rowContents);
373    }
374
375    @Override
376    public void addInheritedMemberSummary(TypeElement tElement,
377            Element nestedClass, boolean isFirst, boolean isLast,
378            Content content) {
379        writer.addInheritedMemberSummary(this, tElement, nestedClass, isFirst,
380            content);
381    }
382
383    @Override
384    public Content getInheritedSummaryHeader(TypeElement tElement) {
385        Content c = writer.getMemberInherited();
386        writer.addInheritedSummaryHeader(this, tElement, c);
387        return c;
388    }
389
390    @Override
391    public Content getInheritedSummaryLinks() {
392        return new HtmlTree(TagName.CODE);
393    }
394
395    @Override
396    public Content getSummaryTable(TypeElement tElement) {
397        if (tElement != typeElement) {
398            throw new IllegalStateException();
399        }
400        return getSummaryTable();
401    }
402
403    @Override
404    public Content getMember(Content memberContent) {
405        return writer.getMember(memberContent);
406    }
407
408    @Override
409    public Content getMemberList() {
410        return writer.getMemberList();
411    }
412
413    @Override
414    public Content getMemberListItem(Content memberContent) {
415        return writer.getMemberListItem(memberContent);
416    }
417
418}