001/*
002 * Copyright (c) 1998, 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.formats.html;
027
028import java.io.IOException;
029import java.io.Writer;
030import java.util.HashMap;
031import java.util.Map;
032import java.util.SortedSet;
033import javax.lang.model.element.Element;
034import javax.lang.model.element.ExecutableElement;
035import javax.lang.model.element.TypeElement;
036
037import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.HtmlTree;
038import org.jdrupes.mdoclet.internal.doclets.formats.html.markup.Links;
039import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources;
040import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile;
041import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException;
042import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath;
043import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths;
044import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexBuilder;
045import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexItem;
046import org.jdrupes.mdoclet.internal.doclets.toolkit.util.Utils;
047
048/**
049 * Extensions to {@code IndexBuilder} to fill in remaining fields
050 * in index items: {@code containingModule}, {@code containingPackage},
051 * {@code containingClass}, and {@code url}, and to write out the
052 * JavaScript files.
053 */
054public class HtmlIndexBuilder extends IndexBuilder {
055    private final HtmlConfiguration configuration;
056
057    private final Resources resources;
058    private final Utils utils;
059    private final HtmlIds htmlIds;
060
061    /**
062     * Creates a new {@code HtmlIndexBuilder}.
063     *
064     * @param configuration the current configuration of the doclet
065     */
066    HtmlIndexBuilder(HtmlConfiguration configuration) {
067        super(configuration, configuration.getOptions().noDeprecated());
068        this.configuration = configuration;
069        resources = configuration.docResources;
070        utils = configuration.utils;
071        htmlIds = configuration.htmlIds;
072    }
073
074    /**
075     * {@inheritDoc}
076     *
077     * After the initial work to add the element items, the remaining fields in
078     * the items are also initialized.
079     */
080    public void addElements() {
081        super.addElements();
082        if (classesOnly) {
083            return;
084        }
085
086        Map<String, Integer> duplicateLabelCheck = new HashMap<>();
087        for (Character ch : getFirstCharacters()) {
088            for (IndexItem item : getItems(ch)) {
089                duplicateLabelCheck.compute(item.getFullyQualifiedLabel(utils),
090                    (k, v) -> v == null ? 1 : v + 1);
091            }
092        }
093
094        for (Character ch : getFirstCharacters()) {
095            for (IndexItem item : getItems(ch)) {
096                if (item.isElementItem()) {
097                    boolean addModuleInfo = duplicateLabelCheck
098                        .get(item.getFullyQualifiedLabel(utils)) > 1;
099                    addContainingInfo(item, addModuleInfo);
100                }
101            }
102        }
103    }
104
105    private void addContainingInfo(IndexItem item, boolean addModuleInfo) {
106        Element element = item.getElement();
107        switch (element.getKind()) {
108        case MODULE:
109            break;
110
111        case PACKAGE:
112            if (configuration.showModules) {
113                item.setContainingModule(utils
114                    .getFullyQualifiedName(utils.containingModule(element)));
115            }
116            break;
117
118        case CLASS:
119        case ENUM:
120        case RECORD:
121        case ANNOTATION_TYPE:
122        case INTERFACE:
123            item.setContainingPackage(
124                utils.getPackageName(utils.containingPackage(element)));
125            if (configuration.showModules && addModuleInfo) {
126                item.setContainingModule(utils
127                    .getFullyQualifiedName(utils.containingModule(element)));
128            }
129            break;
130
131        case CONSTRUCTOR:
132        case METHOD:
133        case FIELD:
134        case ENUM_CONSTANT:
135            TypeElement containingType = item.getContainingTypeElement();
136            item.setContainingPackage(
137                utils.getPackageName(utils.containingPackage(containingType)));
138            item.setContainingClass(utils.getSimpleName(containingType));
139            if (configuration.showModules && addModuleInfo) {
140                item.setContainingModule(utils
141                    .getFullyQualifiedName(utils.containingModule(element)));
142            }
143            if (utils.isExecutableElement(element)) {
144                String url = HtmlTree.encodeURL(
145                    htmlIds.forMember((ExecutableElement) element).name());
146                if (!url.equals(item.getLabel())) {
147                    item.setUrl(url);
148                }
149            }
150            break;
151
152        default:
153            throw new Error();
154        }
155    }
156
157    /**
158     * Generates the set of index files used by interactive search.
159     *
160     * @throws DocFileIOException if there is a problem creating any of the search index files
161     */
162    public void createSearchIndexFiles() throws DocFileIOException {
163        // add last-minute items
164        if (!configuration.packages.isEmpty()) {
165            IndexItem item = IndexItem.of(IndexItem.Category.PACKAGES,
166                resources.getText("doclet.All_Packages"),
167                DocPaths.ALLPACKAGES_INDEX);
168            add(item);
169        }
170        IndexItem item = IndexItem.of(IndexItem.Category.TYPES,
171            resources.getText("doclet.All_Classes_And_Interfaces"),
172            DocPaths.ALLCLASSES_INDEX);
173        add(item);
174
175        for (IndexItem.Category category : IndexItem.Category.values()) {
176            DocPath file;
177            String varName;
178            switch (category) {
179            case MODULES -> {
180                file = DocPaths.MODULE_SEARCH_INDEX_JS;
181                varName = "moduleSearchIndex";
182            }
183            case PACKAGES -> {
184                file = DocPaths.PACKAGE_SEARCH_INDEX_JS;
185                varName = "packageSearchIndex";
186            }
187            case TYPES -> {
188                file = DocPaths.TYPE_SEARCH_INDEX_JS;
189                varName = "typeSearchIndex";
190            }
191            case MEMBERS -> {
192                file = DocPaths.MEMBER_SEARCH_INDEX_JS;
193                varName = "memberSearchIndex";
194            }
195            case TAGS -> {
196                file = DocPaths.TAG_SEARCH_INDEX_JS;
197                varName = "tagSearchIndex";
198            }
199            default -> throw new Error();
200            }
201
202            createSearchIndexFile(file, getItems(category), varName);
203        }
204    }
205
206    /**
207     * Creates a search index file.
208     *
209     * @param searchIndexJS the file for the JavaScript to be generated
210     * @param indexItems    the search index items
211     * @param varName       the variable name to write in the JavaScript file
212     *
213     * @throws DocFileIOException if there is a problem creating the search index file
214     */
215    private void createSearchIndexFile(DocPath searchIndexJS,
216            SortedSet<IndexItem> indexItems,
217            String varName)
218            throws DocFileIOException {
219        // The file needs to be created even if there are no searchIndex items
220        DocFile jsFile
221            = DocFile.createFileForOutput(configuration, searchIndexJS);
222        try (Writer wr = jsFile.openWriter()) {
223            wr.write(varName);
224            wr.write(" = [");
225            boolean first = true;
226            for (IndexItem item : indexItems) {
227                if (first) {
228                    first = false;
229                } else {
230                    wr.write(",");
231                }
232                wr.write(item.toJSON());
233            }
234            wr.write("];");
235            wr.write("updateSearchResults();");
236        } catch (IOException ie) {
237            throw new DocFileIOException(jsFile, DocFileIOException.Mode.WRITE,
238                ie);
239        }
240    }
241}