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 static org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable.Kind.*;
029
030import java.util.*;
031
032import javax.lang.model.element.Element;
033import javax.lang.model.element.PackageElement;
034import javax.lang.model.element.TypeElement;
035import javax.lang.model.element.VariableElement;
036
037import org.jdrupes.mdoclet.internal.doclets.toolkit.ConstantsSummaryWriter;
038import org.jdrupes.mdoclet.internal.doclets.toolkit.Content;
039import org.jdrupes.mdoclet.internal.doclets.toolkit.DocletException;
040import org.jdrupes.mdoclet.internal.doclets.toolkit.util.VisibleMemberTable;
041
042/**
043 * Builds the Constants Summary Page.
044 */
045public class ConstantsSummaryBuilder extends AbstractBuilder {
046
047    /**
048     * The maximum number of package directories shown in the headings of
049     * the constant values contents list and headings.
050     */
051    private static final int MAX_CONSTANT_VALUE_INDEX_LENGTH = 2;
052
053    /**
054     * The writer used to write the results.
055     */
056    protected ConstantsSummaryWriter writer;
057
058    /**
059     * The set of type elements that have constant fields.
060     */
061    protected final Set<TypeElement> typeElementsWithConstFields;
062
063    /**
064     * The set of package-group headings.
065     */
066    protected final Set<String> packageGroupHeadings;
067
068    /**
069     * The current package being documented.
070     */
071    private PackageElement currentPackage;
072
073    /**
074     * The current class being documented.
075     */
076    private TypeElement currentClass;
077
078    /**
079     * Constructs a new {@code ConstantsSummaryBuilder}.
080     *
081     * @param context       the build context
082     */
083    private ConstantsSummaryBuilder(Context context) {
084        super(context);
085        this.typeElementsWithConstFields = new HashSet<>();
086        this.packageGroupHeadings = new TreeSet<>(utils::compareStrings);
087    }
088
089    /**
090     * Constructs a {@code ConstantsSummaryBuilder}.
091     *
092     * @param context       the build context
093     * @return the new ConstantsSummaryBuilder
094     */
095    public static ConstantsSummaryBuilder getInstance(Context context) {
096        return new ConstantsSummaryBuilder(context);
097    }
098
099    @Override
100    public void build() throws DocletException {
101        boolean anyConstants
102            = configuration.packages.stream().anyMatch(this::hasConstantField);
103        if (!anyConstants) {
104            return;
105        }
106
107        writer = configuration.getWriterFactory().getConstantsSummaryWriter();
108        if (writer == null) {
109            // Doclet does not support this output.
110            return;
111        }
112        buildConstantSummary();
113    }
114
115    /**
116     * Builds the constant summary page.
117     *
118     * @throws DocletException if there is a problem while building the documentation
119     */
120    protected void buildConstantSummary() throws DocletException {
121        Content content = writer.getHeader();
122
123        buildContents();
124        buildConstantSummaries();
125
126        writer.addFooter();
127        writer.printDocument(content);
128    }
129
130    /**
131     * Builds the list of contents for the groups of packages appearing in the constants summary page.
132     */
133    protected void buildContents() {
134        Content contentList = writer.getContentsHeader();
135        packageGroupHeadings.clear();
136        for (PackageElement pkg : configuration.packages) {
137            String abbrevPackageName = getAbbrevPackageName(pkg);
138            if (hasConstantField(pkg)
139                && !packageGroupHeadings.contains(abbrevPackageName)) {
140                writer.addLinkToPackageContent(abbrevPackageName, contentList);
141                packageGroupHeadings.add(abbrevPackageName);
142            }
143        }
144        writer.addContentsList(contentList);
145    }
146
147    /**
148     * Builds the summary for each documented package.
149     *
150     * @throws DocletException if there is a problem while building the documentation
151     */
152    protected void buildConstantSummaries() throws DocletException {
153        packageGroupHeadings.clear();
154        Content summaries = writer.getConstantSummaries();
155        for (PackageElement aPackage : configuration.packages) {
156            if (hasConstantField(aPackage)) {
157                currentPackage = aPackage;
158                // Build the documentation for the current package.
159                buildPackageHeader(summaries);
160                buildClassConstantSummary();
161            }
162        }
163        writer.addConstantSummaries(summaries);
164    }
165
166    /**
167     * Builds the header for the given package.
168     *
169     * @param target the content to which the package header will be added
170     */
171    protected void buildPackageHeader(Content target) {
172        String abbrevPkgName = getAbbrevPackageName(currentPackage);
173        if (!packageGroupHeadings.contains(abbrevPkgName)) {
174            writer.addPackageGroup(abbrevPkgName, target);
175            packageGroupHeadings.add(abbrevPkgName);
176        }
177    }
178
179    /**
180     * Builds the summary for the current class.
181     *
182     * @throws DocletException if there is a problem while building the documentation
183     */
184    protected void buildClassConstantSummary()
185            throws DocletException {
186        SortedSet<TypeElement> classes = !currentPackage.isUnnamed()
187            ? utils.getAllClasses(currentPackage)
188            : configuration.typeElementCatalog.allUnnamedClasses();
189        Content classConstantHeader = writer.getClassConstantHeader();
190        for (TypeElement te : classes) {
191            if (!typeElementsWithConstFields.contains(te) ||
192                !utils.isIncluded(te)) {
193                continue;
194            }
195            currentClass = te;
196            // Build the documentation for the current class.
197
198            buildConstantMembers(classConstantHeader);
199
200        }
201        writer.addClassConstant(classConstantHeader);
202    }
203
204    /**
205     * Builds the summary of constant members in the class.
206     *
207     * @param target the content to which the table of constant members will be added
208     */
209    protected void buildConstantMembers(Content target) {
210        new ConstantFieldBuilder(currentClass).buildMembersSummary(target);
211    }
212
213    /**
214     * {@return true if the given package has constant fields to document}
215     *
216     * @param pkg   the package to be checked
217     */
218    private boolean hasConstantField(PackageElement pkg) {
219        SortedSet<TypeElement> classes = !pkg.isUnnamed()
220            ? utils.getAllClasses(pkg)
221            : configuration.typeElementCatalog.allUnnamedClasses();
222        boolean found = false;
223        for (TypeElement te : classes) {
224            if (utils.isIncluded(te) && hasConstantField(te)) {
225                found = true;
226            }
227        }
228        return found;
229    }
230
231    /**
232     * {@return true if the given class has constant fields to document}
233     *
234     * @param typeElement the class to be checked
235     */
236    private boolean hasConstantField(TypeElement typeElement) {
237        VisibleMemberTable vmt
238            = configuration.getVisibleMemberTable(typeElement);
239        List<? extends Element> fields = vmt.getVisibleMembers(FIELDS);
240        for (Element f : fields) {
241            VariableElement field = (VariableElement) f;
242            if (field.getConstantValue() != null) {
243                typeElementsWithConstFields.add(typeElement);
244                return true;
245            }
246        }
247        return false;
248    }
249
250    /**
251     * {@return the abbreviated name for a package, containing the leading segments of the name}
252     *
253     * @param pkg the package
254     */
255    public String getAbbrevPackageName(PackageElement pkg) {
256        if (pkg.isUnnamed()) {
257            return "";
258        }
259
260        String packageName = utils.getPackageName(pkg);
261        int index = -1;
262        for (int j = 0; j < MAX_CONSTANT_VALUE_INDEX_LENGTH; j++) {
263            index = packageName.indexOf(".", index + 1);
264        }
265        return index == -1 ? packageName : packageName.substring(0, index);
266    }
267
268    /**
269     * Builder for the table of fields with constant values.
270     */
271    private class ConstantFieldBuilder {
272
273        /**
274         * The type element that we are examining constants for.
275         */
276        protected TypeElement typeElement;
277
278        /**
279         * Constructs a {@code ConstantFieldBuilder}.
280         * @param typeElement the type element that we are examining constants for
281         */
282        public ConstantFieldBuilder(TypeElement typeElement) {
283            this.typeElement = typeElement;
284        }
285
286        /**
287         * Builds the table of constants for a given class.
288         *
289         * @param target the content to which the table of class constants will be added
290         */
291        protected void buildMembersSummary(Content target) {
292            SortedSet<VariableElement> members = members();
293            if (!members.isEmpty()) {
294                writer.addConstantMembers(typeElement, members, target);
295            }
296        }
297
298        /**
299         * {@return a set of visible constant fields for the given type}
300         */
301        protected SortedSet<VariableElement> members() {
302            VisibleMemberTable vmt
303                = configuration.getVisibleMemberTable(typeElement);
304            List<Element> members = new ArrayList<>();
305            members.addAll(vmt.getVisibleMembers(FIELDS));
306            members.addAll(vmt.getVisibleMembers(ENUM_CONSTANTS));
307            SortedSet<VariableElement> includes = new TreeSet<>(
308                utils.comparators.makeGeneralPurposeComparator());
309            for (Element element : members) {
310                VariableElement member = (VariableElement) element;
311                if (member.getConstantValue() != null) {
312                    includes.add(member);
313                }
314            }
315            return includes;
316        }
317    }
318}