001/*
002 * Copyright (c) 1998, 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.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033import java.util.SortedSet;
034import java.util.TreeSet;
035
036import javax.lang.model.element.Element;
037import javax.lang.model.element.PackageElement;
038import javax.lang.model.element.TypeElement;
039import javax.tools.Diagnostic;
040
041import org.jdrupes.mdoclet.internal.doclets.formats.html.Navigation.PageMode;
042import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder;
043import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlStyle;
044import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree;
045import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.TagName;
046import org.jdrupes.mdoclet.internal.doclets.toolkit.Content;
047import org.jdrupes.mdoclet.internal.doclets.toolkit.util.ClassTree;
048import org.jdrupes.mdoclet.internal.doclets.toolkit.util.ClassUseMapper;
049import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException;
050import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath;
051import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths;
052import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils;
053
054/**
055 * Generate class usage information.
056 */
057public class ClassUseWriter extends SubWriterHolderWriter {
058
059    final TypeElement typeElement;
060    Set<PackageElement> pkgToPackageAnnotations = null;
061    final Map<PackageElement, List<Element>> pkgToClassTypeParameter;
062    final Map<PackageElement, List<Element>> pkgToClassAnnotations;
063    final Map<PackageElement, List<Element>> pkgToMethodTypeParameter;
064    final Map<PackageElement, List<Element>> pkgToMethodArgTypeParameter;
065    final Map<PackageElement, List<Element>> pkgToMethodReturnTypeParameter;
066    final Map<PackageElement, List<Element>> pkgToMethodAnnotations;
067    final Map<PackageElement, List<Element>> pkgToMethodParameterAnnotations;
068    final Map<PackageElement, List<Element>> pkgToFieldTypeParameter;
069    final Map<PackageElement, List<Element>> pkgToFieldAnnotations;
070    final Map<PackageElement, List<Element>> pkgToSubclass;
071    final Map<PackageElement, List<Element>> pkgToSubinterface;
072    final Map<PackageElement, List<Element>> pkgToImplementingClass;
073    final Map<PackageElement, List<Element>> pkgToField;
074    final Map<PackageElement, List<Element>> pkgToMethodReturn;
075    final Map<PackageElement, List<Element>> pkgToMethodArgs;
076    final Map<PackageElement, List<Element>> pkgToMethodThrows;
077    final Map<PackageElement, List<Element>> pkgToConstructorAnnotations;
078    final Map<PackageElement,
079            List<Element>> pkgToConstructorParameterAnnotations;
080    final Map<PackageElement, List<Element>> pkgToConstructorArgs;
081    final Map<PackageElement, List<Element>> pkgToConstructorArgTypeParameter;
082    final Map<PackageElement, List<Element>> pkgToConstructorThrows;
083    final SortedSet<PackageElement> pkgSet;
084    final MethodWriterImpl methodSubWriter;
085    final ConstructorWriterImpl constrSubWriter;
086    final FieldWriterImpl fieldSubWriter;
087    final NestedClassWriterImpl classSubWriter;
088
089    /**
090     * Constructor.
091     *
092     * @param filename the file to be generated.
093     */
094    public ClassUseWriter(HtmlConfiguration configuration,
095            ClassUseMapper mapper, DocPath filename,
096            TypeElement typeElement) {
097        super(configuration, filename);
098        this.typeElement = typeElement;
099        if (mapper.classToPackageAnnotations.containsKey(typeElement)) {
100            pkgToPackageAnnotations
101                = new TreeSet<>(comparators.makeClassUseComparator());
102            pkgToPackageAnnotations
103                .addAll(mapper.classToPackageAnnotations.get(typeElement));
104        }
105        configuration.currentTypeElement = typeElement;
106        this.pkgSet = new TreeSet<>(comparators.makePackageComparator());
107        this.pkgToClassTypeParameter = pkgDivide(mapper.classToClassTypeParam);
108        this.pkgToClassAnnotations = pkgDivide(mapper.classToClassAnnotations);
109        this.pkgToMethodTypeParameter
110            = pkgDivide(mapper.classToMethodTypeParam);
111        this.pkgToMethodArgTypeParameter
112            = pkgDivide(mapper.classToMethodArgTypeParam);
113        this.pkgToFieldTypeParameter = pkgDivide(mapper.classToFieldTypeParam);
114        this.pkgToFieldAnnotations = pkgDivide(mapper.annotationToField);
115        this.pkgToMethodReturnTypeParameter
116            = pkgDivide(mapper.classToMethodReturnTypeParam);
117        this.pkgToMethodAnnotations
118            = pkgDivide(mapper.classToMethodAnnotations);
119        this.pkgToMethodParameterAnnotations
120            = pkgDivide(mapper.classToMethodParamAnnotation);
121        this.pkgToSubclass = pkgDivide(mapper.classToSubclass);
122        this.pkgToSubinterface = pkgDivide(mapper.classToSubinterface);
123        this.pkgToImplementingClass
124            = pkgDivide(mapper.classToImplementingClass);
125        this.pkgToField = pkgDivide(mapper.classToField);
126        this.pkgToMethodReturn = pkgDivide(mapper.classToMethodReturn);
127        this.pkgToMethodArgs = pkgDivide(mapper.classToMethodArgs);
128        this.pkgToMethodThrows = pkgDivide(mapper.classToMethodThrows);
129        this.pkgToConstructorAnnotations
130            = pkgDivide(mapper.classToConstructorAnnotations);
131        this.pkgToConstructorParameterAnnotations
132            = pkgDivide(mapper.classToConstructorParamAnnotation);
133        this.pkgToConstructorArgs = pkgDivide(mapper.classToConstructorArgs);
134        this.pkgToConstructorArgTypeParameter
135            = pkgDivide(mapper.classToConstructorArgTypeParam);
136        this.pkgToConstructorThrows
137            = pkgDivide(mapper.classToConstructorThrows);
138        // tmp test
139        if (pkgSet.size() > 0 &&
140            mapper.classToPackage.containsKey(this.typeElement) &&
141            !pkgSet.equals(mapper.classToPackage.get(this.typeElement))) {
142            configuration.reporter.print(Diagnostic.Kind.WARNING,
143                "Internal error: package sets don't match: "
144                    + pkgSet + " with: "
145                    + mapper.classToPackage.get(this.typeElement));
146        }
147
148        methodSubWriter = new MethodWriterImpl(this);
149        constrSubWriter = new ConstructorWriterImpl(this);
150        constrSubWriter.setFoundNonPubConstructor(true);
151        fieldSubWriter = new FieldWriterImpl(this);
152        classSubWriter = new NestedClassWriterImpl(this);
153    }
154
155    /**
156     * Write out class use pages.
157     *
158     * @param configuration the configuration for this doclet
159     * @param classTree the class tree hierarchy
160     * @throws DocFileIOException if there is an error while generating the documentation
161     */
162    public static void generate(HtmlConfiguration configuration,
163            ClassTree classTree) throws DocFileIOException {
164        ClassUseMapper mapper = new ClassUseMapper(configuration, classTree);
165        boolean nodeprecated = configuration.getOptions().noDeprecated();
166        Utils utils = configuration.utils;
167        for (TypeElement aClass : configuration.getIncludedTypeElements()) {
168            // If -nodeprecated option is set and the containing package is
169            // marked
170            // as deprecated, do not generate the class-use page. We will still
171            // generate
172            // the class-use page if the class is marked as deprecated but the
173            // containing
174            // package is not since it could still be linked from that
175            // package-use page.
176            if (!(nodeprecated &&
177                utils.isDeprecated(utils.containingPackage(aClass))))
178                ClassUseWriter.generate(configuration, mapper, aClass);
179        }
180        for (PackageElement pkg : configuration.packages) {
181            // If -nodeprecated option is set and the package is marked
182            // as deprecated, do not generate the package-use page.
183            if (!(nodeprecated && utils.isDeprecated(pkg)))
184                PackageUseWriter.generate(configuration, mapper, pkg);
185        }
186    }
187
188    private Map<PackageElement, List<Element>> pkgDivide(
189            Map<TypeElement, ? extends List<? extends Element>> classMap) {
190        Map<PackageElement, List<Element>> map = new HashMap<>();
191        List<? extends Element> elements = classMap.get(typeElement);
192        if (elements != null) {
193            elements.sort(comparators.makeClassUseComparator());
194            for (Element e : elements) {
195                PackageElement pkg = utils.containingPackage(e);
196                pkgSet.add(pkg);
197                map.computeIfAbsent(pkg, k -> new ArrayList<>()).add(e);
198            }
199        }
200        return map;
201    }
202
203    /**
204     * Generate a class page.
205     *
206     * @throws DocFileIOException if there is a problem while generating the documentation
207     */
208    public static void generate(HtmlConfiguration configuration,
209            ClassUseMapper mapper,
210            TypeElement typeElement) throws DocFileIOException {
211        ClassUseWriter clsgen;
212        DocPath path = configuration.docPaths.forPackage(typeElement)
213            .resolve(DocPaths.CLASS_USE)
214            .resolve(configuration.docPaths.forName(typeElement));
215        clsgen = new ClassUseWriter(configuration, mapper, path, typeElement);
216        clsgen.generateClassUseFile();
217    }
218
219    /**
220     * Generate the class use elements.
221     *
222     * @throws DocFileIOException if there is a problem while generating the documentation
223     */
224    protected void generateClassUseFile() throws DocFileIOException {
225        HtmlTree body = getClassUseHeader();
226        Content mainContent = new ContentBuilder();
227        if (pkgSet.size() > 0) {
228            addClassUse(mainContent);
229        } else {
230            mainContent.add(contents.getContent("doclet.ClassUse_No.usage.of.0",
231                utils.getFullyQualifiedName(typeElement)));
232        }
233        bodyContents.addMainContent(mainContent);
234        bodyContents.setFooter(getFooter());
235        body.add(bodyContents);
236        String description = getDescription("use", typeElement);
237        printHtmlDocument(null, description, body);
238    }
239
240    /**
241     * Add the class use documentation.
242     *
243     * @param content the content to which the class use information will be added
244     */
245    protected void addClassUse(Content content) {
246        Content c = new ContentBuilder();
247        if (configuration.packages.size() > 1) {
248            addPackageList(c);
249            addPackageAnnotationList(c);
250        }
251        addClassList(c);
252        content.add(c);
253    }
254
255    /**
256     * Add the packages elements that use the given class.
257     *
258     * @param content the content to which the packages elements will be added
259     */
260    protected void addPackageList(Content content) {
261        Content caption = contents.getContent(
262            "doclet.ClassUse_Packages.that.use.0",
263            getLink(new HtmlLinkInfo(configuration,
264                HtmlLinkInfo.Kind.PLAIN, typeElement)));
265        var table = new Table<Void>(HtmlStyle.summaryTable)
266            .setCaption(caption)
267            .setHeader(getPackageTableHeader())
268            .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast);
269        for (PackageElement pkg : pkgSet) {
270            addPackageUse(pkg, table);
271        }
272        content.add(table);
273    }
274
275    /**
276     * Add the package annotation elements.
277     *
278     * @param content the content to which the package annotation elements will be added
279     */
280    protected void addPackageAnnotationList(Content content) {
281        if (!utils.isAnnotationInterface(typeElement) ||
282            pkgToPackageAnnotations == null ||
283            pkgToPackageAnnotations.isEmpty()) {
284            return;
285        }
286        Content caption = contents.getContent(
287            "doclet.ClassUse_PackageAnnotation",
288            getLink(new HtmlLinkInfo(configuration,
289                HtmlLinkInfo.Kind.PLAIN, typeElement)));
290
291        var table = new Table<Void>(HtmlStyle.summaryTable)
292            .setCaption(caption)
293            .setHeader(getPackageTableHeader())
294            .setColumnStyles(HtmlStyle.colFirst, HtmlStyle.colLast);
295        for (PackageElement pkg : pkgToPackageAnnotations) {
296            Content summary = new ContentBuilder();
297            addSummaryComment(pkg, summary);
298            table.addRow(getPackageLink(pkg, getLocalizedPackageName(pkg)),
299                summary);
300        }
301        content.add(table);
302    }
303
304    /**
305     * Add the class elements that use the given class.
306     *
307     * @param content the content to which the class elements will be added
308     */
309    protected void addClassList(Content content) {
310        var ul = HtmlTree.UL(HtmlStyle.blockList);
311        for (PackageElement pkg : pkgSet) {
312            var section = HtmlTree.SECTION(HtmlStyle.detail)
313                .setId(htmlIds.forPackage(pkg));
314            Content link = contents.getContent("doclet.ClassUse_Uses.of.0.in.1",
315                getLink(new HtmlLinkInfo(configuration, HtmlLinkInfo.Kind.PLAIN,
316                    typeElement)),
317                getPackageLink(pkg, getLocalizedPackageName(pkg)));
318            var heading
319                = HtmlTree.HEADING(Headings.TypeUse.SUMMARY_HEADING, link);
320            section.add(heading);
321            addClassUse(pkg, section);
322            ul.add(HtmlTree.LI(section));
323        }
324        var li = HtmlTree.SECTION(HtmlStyle.classUses, ul);
325        content.add(li);
326    }
327
328    /**
329     * Add the package use information.
330     *
331     * @param pkg the package that uses the given class
332     * @param table the table to which the package use information will be added
333     */
334    protected void addPackageUse(PackageElement pkg, Table<?> table) {
335        Content pkgLink = links.createLink(htmlIds.forPackage(pkg),
336            getLocalizedPackageName(pkg));
337        Content summary = new ContentBuilder();
338        addSummaryComment(pkg, summary);
339        table.addRow(pkgLink, summary);
340    }
341
342    /**
343     * Add the class use information.
344     *
345     * @param pkg the package that uses the given class
346     * @param content the content to which the class use information will be added
347     */
348    protected void addClassUse(PackageElement pkg, Content content) {
349        Content classLink = getLink(new HtmlLinkInfo(configuration,
350            HtmlLinkInfo.Kind.PLAIN, typeElement));
351        Content pkgLink = getPackageLink(pkg, getLocalizedPackageName(pkg));
352        classSubWriter.addUseInfo(pkgToClassAnnotations.get(pkg),
353            contents.getContent("doclet.ClassUse_Annotation", classLink,
354                pkgLink),
355            content);
356        classSubWriter.addUseInfo(pkgToClassTypeParameter.get(pkg),
357            contents.getContent("doclet.ClassUse_TypeParameter", classLink,
358                pkgLink),
359            content);
360        classSubWriter.addUseInfo(pkgToSubclass.get(pkg),
361            contents.getContent("doclet.ClassUse_Subclass", classLink,
362                pkgLink),
363            content);
364        classSubWriter.addUseInfo(pkgToSubinterface.get(pkg),
365            contents.getContent("doclet.ClassUse_Subinterface", classLink,
366                pkgLink),
367            content);
368        classSubWriter.addUseInfo(pkgToImplementingClass.get(pkg),
369            contents.getContent("doclet.ClassUse_ImplementingClass", classLink,
370                pkgLink),
371            content);
372        fieldSubWriter.addUseInfo(pkgToField.get(pkg),
373            contents.getContent("doclet.ClassUse_Field", classLink,
374                pkgLink),
375            content);
376        fieldSubWriter.addUseInfo(pkgToFieldAnnotations.get(pkg),
377            contents.getContent("doclet.ClassUse_FieldAnnotations", classLink,
378                pkgLink),
379            content);
380        fieldSubWriter.addUseInfo(pkgToFieldTypeParameter.get(pkg),
381            contents.getContent("doclet.ClassUse_FieldTypeParameter", classLink,
382                pkgLink),
383            content);
384        methodSubWriter.addUseInfo(pkgToMethodAnnotations.get(pkg),
385            contents.getContent("doclet.ClassUse_MethodAnnotations", classLink,
386                pkgLink),
387            content);
388        methodSubWriter.addUseInfo(pkgToMethodParameterAnnotations.get(pkg),
389            contents.getContent("doclet.ClassUse_MethodParameterAnnotations",
390                classLink,
391                pkgLink),
392            content);
393        methodSubWriter.addUseInfo(pkgToMethodTypeParameter.get(pkg),
394            contents.getContent("doclet.ClassUse_MethodTypeParameter",
395                classLink,
396                pkgLink),
397            content);
398        methodSubWriter.addUseInfo(pkgToMethodReturn.get(pkg),
399            contents.getContent("doclet.ClassUse_MethodReturn", classLink,
400                pkgLink),
401            content);
402        methodSubWriter.addUseInfo(pkgToMethodReturnTypeParameter.get(pkg),
403            contents.getContent("doclet.ClassUse_MethodReturnTypeParameter",
404                classLink,
405                pkgLink),
406            content);
407        methodSubWriter.addUseInfo(pkgToMethodArgs.get(pkg),
408            contents.getContent("doclet.ClassUse_MethodArgs", classLink,
409                pkgLink),
410            content);
411        methodSubWriter.addUseInfo(pkgToMethodArgTypeParameter.get(pkg),
412            contents.getContent("doclet.ClassUse_MethodArgsTypeParameters",
413                classLink,
414                pkgLink),
415            content);
416        methodSubWriter.addUseInfo(pkgToMethodThrows.get(pkg),
417            contents.getContent("doclet.ClassUse_MethodThrows", classLink,
418                pkgLink),
419            content);
420        constrSubWriter.addUseInfo(pkgToConstructorAnnotations.get(pkg),
421            contents.getContent("doclet.ClassUse_ConstructorAnnotations",
422                classLink,
423                pkgLink),
424            content);
425        constrSubWriter.addUseInfo(
426            pkgToConstructorParameterAnnotations.get(pkg),
427            contents.getContent(
428                "doclet.ClassUse_ConstructorParameterAnnotations", classLink,
429                pkgLink),
430            content);
431        constrSubWriter.addUseInfo(pkgToConstructorArgs.get(pkg),
432            contents.getContent("doclet.ClassUse_ConstructorArgs", classLink,
433                pkgLink),
434            content);
435        constrSubWriter.addUseInfo(pkgToConstructorArgTypeParameter.get(pkg),
436            contents.getContent("doclet.ClassUse_ConstructorArgsTypeParameters",
437                classLink,
438                pkgLink),
439            content);
440        constrSubWriter.addUseInfo(pkgToConstructorThrows.get(pkg),
441            contents.getContent("doclet.ClassUse_ConstructorThrows", classLink,
442                pkgLink),
443            content);
444    }
445
446    /**
447     * Get the header for the class use listing.
448     *
449     * @return the class use header
450     */
451    protected HtmlTree getClassUseHeader() {
452        String cltype = resources.getText(switch (typeElement.getKind()) {
453        case ANNOTATION_TYPE -> "doclet.AnnotationType";
454        case INTERFACE -> "doclet.Interface";
455        case RECORD -> "doclet.RecordClass";
456        case ENUM -> "doclet.Enum";
457        default -> "doclet.Class";
458        });
459        String clname = utils.getFullyQualifiedName(typeElement);
460        String title = resources.getText("doclet.Window_ClassUse_Header",
461            cltype, clname);
462        HtmlTree body = getBody(getWindowTitle(title));
463        ContentBuilder headingContent = new ContentBuilder();
464        headingContent
465            .add(contents.getContent("doclet.ClassUse_Title", cltype));
466        headingContent.add(new HtmlTree(TagName.BR));
467        headingContent.add(clname);
468        var heading = HtmlTree.HEADING_TITLE(Headings.PAGE_TITLE_HEADING,
469            HtmlStyle.title, headingContent);
470        var div = HtmlTree.DIV(HtmlStyle.header, heading);
471        bodyContents.setHeader(getHeader(PageMode.USE, typeElement))
472            .addMainContent(div);
473        return body;
474    }
475
476    @Override
477    protected Navigation getNavBar(PageMode pageMode, Element element) {
478        Content mdleLinkContent
479            = getModuleLink(utils.elementUtils.getModuleOf(typeElement),
480                contents.moduleLabel);
481        Content classLinkContent = getLink(new HtmlLinkInfo(
482            configuration, HtmlLinkInfo.Kind.PLAIN, typeElement)
483                .label(resources.getText("doclet.Class"))
484                .skipPreview(true));
485        return super.getNavBar(pageMode, element)
486            .setNavLinkModule(mdleLinkContent)
487            .setNavLinkClass(classLinkContent);
488    }
489}