001/*
002 * Copyright (c) 2003, 2022, 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.toolkit.builders;
027
028import java.util.List;
029import java.util.Set;
030import java.util.stream.Collectors;
031
032import javax.lang.model.element.Element;
033import javax.lang.model.element.ExecutableElement;
034import javax.lang.model.element.Name;
035import javax.lang.model.element.PackageElement;
036import javax.lang.model.element.TypeElement;
037import javax.lang.model.element.VariableElement;
038import javax.lang.model.type.TypeMirror;
039
040import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.ContentBuilder;
041import org.jdrupes.mdoclet.internal.doclets.toolkit.ClassWriter;
042import org.jdrupes.mdoclet.internal.doclets.toolkit.CommentUtils;
043import org.jdrupes.mdoclet.internal.doclets.toolkit.Content;
044import org.jdrupes.mdoclet.internal.doclets.toolkit.DocFilesHandler;
045import org.jdrupes.mdoclet.internal.doclets.toolkit.DocletException;
046import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException;
047import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils;
048
049/**
050 * Builds the summary for a given class.
051 */
052public class ClassBuilder extends AbstractBuilder {
053
054    /**
055     * The class being documented.
056     */
057    private final TypeElement typeElement;
058
059    /**
060     * The doclet specific writer.
061     */
062    private final ClassWriter writer;
063
064    private final Utils utils;
065
066    /**
067     * Construct a new ClassBuilder.
068     *
069     * @param context  the build context
070     * @param typeElement the class being documented.
071     * @param writer the doclet specific writer.
072     */
073    private ClassBuilder(Context context, TypeElement typeElement,
074            ClassWriter writer) {
075        super(context);
076        this.typeElement = typeElement;
077        this.writer = writer;
078        this.utils = configuration.utils;
079        switch (typeElement.getKind()) {
080        case ENUM -> setEnumDocumentation(typeElement);
081        case RECORD -> setRecordDocumentation(typeElement);
082        }
083    }
084
085    /**
086     * Constructs a new ClassBuilder.
087     *
088     * @param context  the build context
089     * @param typeElement the class being documented.
090     * @param writer the doclet specific writer.
091     * @return the new ClassBuilder
092     */
093    public static ClassBuilder getInstance(Context context,
094            TypeElement typeElement, ClassWriter writer) {
095        return new ClassBuilder(context, typeElement, writer);
096    }
097
098    @Override
099    public void build() throws DocletException {
100        buildClassDoc();
101    }
102
103    /**
104     * Handles the {@literal <TypeElement>} tag.
105     *
106     * @throws DocletException if there is a problem while building the documentation
107     */
108    protected void buildClassDoc() throws DocletException {
109        String key = switch (typeElement.getKind()) {
110        case INTERFACE -> "doclet.Interface";
111        case ENUM -> "doclet.Enum";
112        case RECORD -> "doclet.RecordClass";
113        case ANNOTATION_TYPE -> "doclet.AnnotationType";
114        case CLASS -> "doclet.Class";
115        default -> throw new IllegalStateException(
116            typeElement.getKind() + " " + typeElement);
117        };
118        Content content = writer.getHeader(resources.getText(key) + " "
119            + utils.getSimpleName(typeElement));
120        Content classContent = writer.getClassContentHeader();
121
122        buildClassTree(classContent);
123        buildClassInfo(classContent);
124        buildMemberSummary(classContent);
125        buildMemberDetails(classContent);
126
127        writer.addClassContent(classContent);
128        writer.addFooter();
129        writer.printDocument(content);
130        copyDocFiles();
131    }
132
133    /**
134     * Build the class inheritance tree documentation.
135     *
136     * @param classContent the content to which the documentation will be added
137     */
138    protected void buildClassTree(Content classContent) {
139        writer.addClassTree(classContent);
140    }
141
142    /**
143     * Build the class information documentation.
144     *
145     * @param target the content to which the documentation will be added
146     * @throws DocletException if there is a problem while building the documentation
147     */
148    protected void buildClassInfo(Content target) throws DocletException {
149        Content c = new ContentBuilder();
150        buildParamInfo(c);
151        buildSuperInterfacesInfo(c);
152        buildImplementedInterfacesInfo(c);
153        buildSubClassInfo(c);
154        buildSubInterfacesInfo(c);
155        buildInterfaceUsageInfo(c);
156        buildNestedClassInfo(c);
157        buildFunctionalInterfaceInfo(c);
158        buildClassSignature(c);
159        buildDeprecationInfo(c);
160        buildClassDescription(c);
161        buildClassTagInfo(c);
162
163        target.add(writer.getClassInfo(c));
164    }
165
166    /**
167     * Build the type parameters and state components of this class.
168     *
169     * @param target the content to which the documentation will be added
170     */
171    protected void buildParamInfo(Content target) {
172        writer.addParamInfo(target);
173    }
174
175    /**
176     * If this is an interface, list all superinterfaces.
177     *
178     * @param target the content to which the documentation will be added
179     */
180    protected void buildSuperInterfacesInfo(Content target) {
181        writer.addSuperInterfacesInfo(target);
182    }
183
184    /**
185     * If this is a class, list all interfaces implemented by this class.
186     *
187     * @param target the content to which the documentation will be added
188     */
189    protected void buildImplementedInterfacesInfo(Content target) {
190        writer.addImplementedInterfacesInfo(target);
191    }
192
193    /**
194     * List all the classes that extend this one.
195     *
196     * @param target the content to which the documentation will be added
197     */
198    protected void buildSubClassInfo(Content target) {
199        writer.addSubClassInfo(target);
200    }
201
202    /**
203     * List all the interfaces that extend this one.
204     *
205     * @param target the content to which the documentation will be added
206     */
207    protected void buildSubInterfacesInfo(Content target) {
208        writer.addSubInterfacesInfo(target);
209    }
210
211    /**
212     * If this is an interface, list all classes that implement this interface.
213     *
214     * @param target the content to which the documentation will be added
215     */
216    protected void buildInterfaceUsageInfo(Content target) {
217        writer.addInterfaceUsageInfo(target);
218    }
219
220    /**
221     * If this is an functional interface, display appropriate message.
222     *
223     * @param target the content to which the documentation will be added
224     */
225    protected void buildFunctionalInterfaceInfo(Content target) {
226        writer.addFunctionalInterfaceInfo(target);
227    }
228
229    /**
230     * If this class is deprecated, build the appropriate information.
231     *
232     * @param target the content to which the documentation will be added
233     */
234    protected void buildDeprecationInfo(Content target) {
235        writer.addClassDeprecationInfo(target);
236    }
237
238    /**
239     * If this is an inner class or interface, list the enclosing class or interface.
240     *
241     * @param target the content to which the documentation will be added
242     */
243    protected void buildNestedClassInfo(Content target) {
244        writer.addNestedClassInfo(target);
245    }
246
247    /**
248     * Copy the doc files.
249     *
250     * @throws DocFileIOException if there is a problem while copying the files
251     */
252    private void copyDocFiles() throws DocletException {
253        PackageElement containingPackage = utils.containingPackage(typeElement);
254        if ((configuration.packages == null ||
255            !configuration.packages.contains(containingPackage)) &&
256            !containingPackagesSeen.contains(containingPackage)) {
257            // Only copy doc files dir if the containing package is not
258            // documented AND if we have not documented a class from the same
259            // package already. Otherwise, we are making duplicate copies.
260            DocFilesHandler docFilesHandler = configuration
261                .getWriterFactory()
262                .getDocFilesHandler(containingPackage);
263            docFilesHandler.copyDocFiles();
264            containingPackagesSeen.add(containingPackage);
265        }
266    }
267
268    /**
269     * Build the signature of the current class.
270     *
271     * @param target the content to which the documentation will be added
272     */
273    protected void buildClassSignature(Content target) {
274        writer.addClassSignature(target);
275    }
276
277    /**
278     * Build the class description.
279     *
280     * @param target the content to which the documentation will be added
281     */
282    protected void buildClassDescription(Content target) {
283        writer.addClassDescription(target);
284    }
285
286    /**
287     * Build the tag information for the current class.
288     *
289     * @param target the content to which the documentation will be added
290     */
291    protected void buildClassTagInfo(Content target) {
292        writer.addClassTagInfo(target);
293    }
294
295    /**
296     * Build the member summary contents of the page.
297     *
298     * @param classContent the content to which the documentation will be added
299     * @throws DocletException if there is a problem while building the documentation
300     */
301    protected void buildMemberSummary(Content classContent)
302            throws DocletException {
303        Content summariesList = writer.getSummariesList();
304        builderFactory.getMemberSummaryBuilder(writer).build(summariesList);
305        classContent.add(writer.getMemberSummary(summariesList));
306    }
307
308    /**
309     * Build the member details contents of the page.
310     *
311     * @param classContent the content to which the documentation will be added
312     * @throws DocletException if there is a problem while building the documentation
313     */
314    protected void buildMemberDetails(Content classContent)
315            throws DocletException {
316        Content detailsList = writer.getDetailsList();
317
318        buildEnumConstantsDetails(detailsList);
319        buildPropertyDetails(detailsList);
320        buildFieldDetails(detailsList);
321        buildConstructorDetails(detailsList);
322        buildAnnotationTypeMemberDetails(detailsList);
323        buildMethodDetails(detailsList);
324
325        classContent.add(writer.getMemberDetails(detailsList));
326    }
327
328    /**
329     * Build the enum constants documentation.
330     *
331     * @param detailsList the content to which the documentation will be added
332     * @throws DocletException if there is a problem while building the documentation
333     */
334    protected void buildEnumConstantsDetails(Content detailsList)
335            throws DocletException {
336        builderFactory.getEnumConstantsBuilder(writer).build(detailsList);
337    }
338
339    /**
340     * Build the field documentation.
341     *
342     * @param detailsList the content to which the documentation will be added
343     * @throws DocletException if there is a problem while building the documentation
344     */
345    protected void buildFieldDetails(Content detailsList)
346            throws DocletException {
347        builderFactory.getFieldBuilder(writer).build(detailsList);
348    }
349
350    /**
351     * Build the property documentation.
352     *
353     * @param detailsList the content to which the documentation will be added
354     * @throws DocletException if there is a problem while building the documentation
355     */
356    public void buildPropertyDetails(Content detailsList)
357            throws DocletException {
358        builderFactory.getPropertyBuilder(writer).build(detailsList);
359    }
360
361    /**
362     * Build the constructor documentation.
363     *
364     * @param detailsList the content to which the documentation will be added
365     * @throws DocletException if there is a problem while building the documentation
366     */
367    protected void buildConstructorDetails(Content detailsList)
368            throws DocletException {
369        builderFactory.getConstructorBuilder(writer).build(detailsList);
370    }
371
372    /**
373     * Build the method documentation.
374     *
375     * @param detailsList the content to which the documentation will be added
376     * @throws DocletException if there is a problem while building the documentation
377     */
378    protected void buildMethodDetails(Content detailsList)
379            throws DocletException {
380        builderFactory.getMethodBuilder(writer).build(detailsList);
381    }
382
383    /**
384     * Build the annotation type optional member documentation.
385     *
386     * @param target the content to which the documentation will be added
387     * @throws DocletException if there is a problem building the documentation
388     */
389    protected void buildAnnotationTypeMemberDetails(Content target)
390            throws DocletException {
391        builderFactory.getAnnotationTypeMemberBuilder(writer).build(target);
392    }
393
394    /**
395     * The documentation for values() and valueOf() in Enums are set by the
396     * doclet only iff the user or overridden methods are missing.
397     * @param elem the enum element
398     */
399    private void setEnumDocumentation(TypeElement elem) {
400        CommentUtils cmtUtils = configuration.cmtUtils;
401        for (ExecutableElement ee : utils.getMethods(elem)) {
402            if (!utils.getFullBody(ee).isEmpty()) // ignore if already set
403                continue;
404            Name name = ee.getSimpleName();
405            if (name.contentEquals("values") && ee.getParameters().isEmpty()) {
406                utils.removeCommentHelper(ee); // purge previous entry
407                cmtUtils.setEnumValuesTree(ee);
408            } else if (name.contentEquals("valueOf")
409                && ee.getParameters().size() == 1) {
410                // TODO: check parameter type
411                utils.removeCommentHelper(ee); // purge previous entry
412                cmtUtils.setEnumValueOfTree(ee);
413            }
414        }
415    }
416
417    /**
418     * Sets the documentation as needed for the mandated parts of a record type.
419     * This includes the canonical constructor, methods like {@code equals},
420     * {@code hashCode}, {@code toString}, the accessor methods, and the underlying
421     * field.
422     * @param elem the record element
423     */
424
425    private void setRecordDocumentation(TypeElement elem) {
426        CommentUtils cmtUtils = configuration.cmtUtils;
427        Set<Name> componentNames = elem.getRecordComponents().stream()
428            .map(Element::getSimpleName)
429            .collect(Collectors.toSet());
430
431        for (ExecutableElement ee : utils.getConstructors(elem)) {
432            if (utils.isCanonicalRecordConstructor(ee)) {
433                if (utils.getFullBody(ee).isEmpty()) {
434                    utils.removeCommentHelper(ee); // purge previous entry
435                    cmtUtils.setRecordConstructorTree(ee);
436                }
437                // only one canonical constructor; no need to keep looking
438                break;
439            }
440        }
441
442        var fields = utils.isSerializable(elem)
443            ? utils.getFieldsUnfiltered(elem)
444            : utils.getFields(elem);
445        for (VariableElement ve : fields) {
446            // The fields for the record component cannot be declared by the
447            // user and so cannot have any pre-existing comment.
448            Name name = ve.getSimpleName();
449            if (componentNames.contains(name)) {
450                utils.removeCommentHelper(ve); // purge previous entry
451                cmtUtils.setRecordFieldTree(ve);
452            }
453        }
454
455        TypeMirror objectType = utils.getObjectType();
456
457        for (ExecutableElement ee : utils.getMethods(elem)) {
458            if (!utils.getFullBody(ee).isEmpty()) {
459                continue;
460            }
461
462            Name name = ee.getSimpleName();
463            List<? extends VariableElement> params = ee.getParameters();
464            if (name.contentEquals("equals")) {
465                if (params.size() == 1 && utils.typeUtils
466                    .isSameType(params.get(0).asType(), objectType)) {
467                    utils.removeCommentHelper(ee); // purge previous entry
468                    cmtUtils.setRecordEqualsTree(ee);
469                }
470            } else if (name.contentEquals("hashCode")) {
471                if (params.isEmpty()) {
472                    utils.removeCommentHelper(ee); // purge previous entry
473                    cmtUtils.setRecordHashCodeTree(ee);
474                }
475            } else if (name.contentEquals("toString")) {
476                if (params.isEmpty()) {
477                    utils.removeCommentHelper(ee); // purge previous entry
478                    cmtUtils.setRecordToStringTree(ee);
479                }
480            } else if (componentNames.contains(name)) {
481                if (params.isEmpty()) {
482                    utils.removeCommentHelper(ee); // purge previous entry
483                    cmtUtils.setRecordAccessorTree(ee);
484                }
485            }
486        }
487
488    }
489}