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.io.IOException;
029import java.nio.file.DirectoryStream;
030import java.nio.file.Files;
031import java.nio.file.InvalidPathException;
032import java.nio.file.Path;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.HashMap;
036import java.util.List;
037import java.util.Locale;
038import java.util.Map;
039import java.util.Set;
040import java.util.SortedSet;
041import java.util.function.Function;
042
043import javax.lang.model.SourceVersion;
044import javax.lang.model.element.ModuleElement;
045import javax.lang.model.element.PackageElement;
046import javax.lang.model.element.TypeElement;
047
048import org.jdrupes.mdoclet.internal.doclets.toolkit.AbstractDoclet;
049import org.jdrupes.mdoclet.internal.doclets.toolkit.DocletException;
050import org.jdrupes.mdoclet.internal.doclets.toolkit.Messages;
051import org.jdrupes.mdoclet.internal.doclets.toolkit.builders.AbstractBuilder;
052import org.jdrupes.mdoclet.internal.doclets.toolkit.builders.BuilderFactory;
053import org.jdrupes.mdoclet.internal.doclets.toolkit.util.ClassTree;
054import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DeprecatedAPIListBuilder;
055import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFile;
056import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocFileIOException;
057import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPath;
058import org.jdrupes.mdoclet.internal.doclets.toolkit.util.DocPaths;
059import org.jdrupes.mdoclet.internal.doclets.toolkit.util.IndexBuilder;
060import org.jdrupes.mdoclet.internal.doclets.toolkit.util.NewAPIBuilder;
061import org.jdrupes.mdoclet.internal.doclets.toolkit.util.PreviewAPIListBuilder;
062
063import jdk.javadoc.doclet.Doclet;
064import jdk.javadoc.doclet.DocletEnvironment;
065import jdk.javadoc.doclet.Reporter;
066
067/**
068 * The class with "start" method, calls individual Writers.
069 */
070public class HtmlDoclet extends AbstractDoclet {
071
072    /**
073     * Creates a doclet to generate HTML documentation,
074     * specifying the "initiating doclet" to be used when
075     * initializing any taglets for this doclet.
076     * An initiating doclet is one that delegates to
077     * this doclet.
078     *
079     * @param initiatingDoclet the initiating doclet
080     */
081    public HtmlDoclet(Doclet initiatingDoclet) {
082        this.initiatingDoclet = initiatingDoclet;
083    }
084
085    @Override // defined by Doclet
086    public String getName() {
087        return "Html";
088    }
089
090    /**
091     * The initiating doclet, to be specified when creating
092     * the configuration.
093     */
094    private final Doclet initiatingDoclet;
095
096    /**
097     * The global configuration information for this run.
098     * Initialized in {@link #init(Locale, Reporter)}.
099     */
100    private HtmlConfiguration configuration;
101
102    /**
103     * Object for generating messages and diagnostics.
104     */
105    private Messages messages;
106
107    /**
108     * Base path for resources for this doclet.
109     */
110    private static final DocPath DOCLET_RESOURCES = DocPath
111        .create("/jdk/javadoc/internal/doclets/formats/html/resources");
112
113    @Override // defined by Doclet
114    public void init(Locale locale, Reporter reporter) {
115        configuration
116            = new HtmlConfiguration(initiatingDoclet, locale, reporter);
117        messages = configuration.getMessages();
118    }
119
120    /**
121     * Create the configuration instance.
122     * Override this method to use a different
123     * configuration.
124     *
125     * @return the configuration
126     */
127    @Override // defined by AbstractDoclet
128    public HtmlConfiguration getConfiguration() {
129        return configuration;
130    }
131
132    @Override
133    protected Function<String, String>
134            getResourceKeyMapper(DocletEnvironment docEnv) {
135        SourceVersion sv = docEnv.getSourceVersion();
136        Map<String, String> map = new HashMap<>();
137        String[][] pairs = {
138            // in standard.properties
139            { "doclet.Enum_Hierarchy", "doclet.Enum_Class_Hierarchy" },
140            { "doclet.Annotation_Type_Hierarchy",
141                "doclet.Annotation_Interface_Hierarchy" },
142            { "doclet.Href_Annotation_Title",
143                "doclet.Href_Annotation_Interface_Title" },
144            { "doclet.Href_Enum_Title", "doclet.Href_Enum_Class_Title" },
145            { "doclet.Annotation_Types", "doclet.Annotation_Interfaces" },
146            { "doclet.Annotation_Type_Members",
147                "doclet.Annotation_Interface_Members" },
148            { "doclet.annotation_types", "doclet.annotation_interfaces" },
149            { "doclet.annotation_type_members",
150                "doclet.annotation_interface_members" },
151            { "doclet.help.enum.intro", "doclet.help.enum.class.intro" },
152            { "doclet.help.annotation_type.intro",
153                "doclet.help.annotation_interface.intro" },
154            { "doclet.help.annotation_type.declaration",
155                "doclet.help.annotation_interface.declaration" },
156            { "doclet.help.annotation_type.description",
157                "doclet.help.annotation_interface.description" },
158
159            // in doclets.properties
160            { "doclet.Enums", "doclet.EnumClasses" },
161            { "doclet.AnnotationType", "doclet.AnnotationInterface" },
162            { "doclet.AnnotationTypes", "doclet.AnnotationInterfaces" },
163            { "doclet.annotationtype", "doclet.annotationinterface" },
164            { "doclet.annotationtypes", "doclet.annotationinterfaces" },
165            { "doclet.Enum", "doclet.EnumClass" },
166            { "doclet.enum", "doclet.enumclass" },
167            { "doclet.enums", "doclet.enumclasses" },
168            { "doclet.Annotation_Type_Member",
169                "doclet.Annotation_Interface_Member" },
170            { "doclet.enum_values_doc.fullbody",
171                "doclet.enum_class_values_doc.fullbody" },
172            { "doclet.enum_values_doc.return",
173                "doclet.enum_class_values_doc.return" },
174            { "doclet.enum_valueof_doc.fullbody",
175                "doclet.enum_class_valueof_doc.fullbody" },
176            { "doclet.enum_valueof_doc.throws_ila",
177                "doclet.enum_class_valueof_doc.throws_ila" },
178            { "doclet.search.types", "doclet.search.classes_and_interfaces" }
179        };
180        for (String[] pair : pairs) {
181            if (sv.compareTo(SourceVersion.RELEASE_16) >= 0) {
182                map.put(pair[0], pair[1]);
183            } else {
184                map.put(pair[1], pair[0]);
185            }
186        }
187        return (k) -> map.getOrDefault(k, k);
188    }
189
190    @Override // defined by AbstractDoclet
191    public void generateClassFiles(ClassTree classTree) throws DocletException {
192        List<String> since = configuration.getOptions().since();
193        if (!(configuration.getOptions().noDeprecated()
194            || configuration.getOptions().noDeprecatedList())) {
195            DeprecatedAPIListBuilder deprecatedBuilder
196                = new DeprecatedAPIListBuilder(configuration, since);
197            if (!deprecatedBuilder.isEmpty()) {
198                configuration.deprecatedAPIListBuilder = deprecatedBuilder;
199                configuration.conditionalPages
200                    .add(HtmlConfiguration.ConditionalPage.DEPRECATED);
201            }
202        }
203        if (!since.isEmpty()) {
204            NewAPIBuilder newAPIBuilder
205                = new NewAPIBuilder(configuration, since);
206            if (!newAPIBuilder.isEmpty()) {
207                configuration.newAPIPageBuilder = newAPIBuilder;
208                configuration.conditionalPages
209                    .add(HtmlConfiguration.ConditionalPage.NEW);
210            }
211        }
212        PreviewAPIListBuilder previewBuilder
213            = new PreviewAPIListBuilder(configuration);
214        if (!previewBuilder.isEmpty()) {
215            configuration.previewAPIListBuilder = previewBuilder;
216            configuration.conditionalPages
217                .add(HtmlConfiguration.ConditionalPage.PREVIEW);
218        }
219
220        super.generateClassFiles(classTree);
221    }
222
223    /**
224     * Start the generation of files. Call generate methods in the individual
225     * writers, which will in turn generate the documentation files. Call the
226     * TreeWriter generation first to ensure the Class Hierarchy is built
227     * first and then can be used in the later generation.
228     *
229     * For new format.
230     *
231     * @throws DocletException if there is a problem while writing the other files
232     */
233    @Override // defined by AbstractDoclet
234    protected void generateOtherFiles(ClassTree classTree)
235            throws DocletException {
236        super.generateOtherFiles(classTree);
237        HtmlOptions options = configuration.getOptions();
238        if (options.linkSource()) {
239            SourceToHTMLConverter.convertRoot(configuration,
240                DocPaths.SOURCE_OUTPUT);
241        }
242        // Modules with no documented classes may be specified on the
243        // command line to specify a service provider, allow these.
244        if (configuration.getSpecifiedModuleElements().isEmpty() &&
245            configuration.topFile.isEmpty()) {
246            messages.error("doclet.No_Non_Deprecated_Classes_To_Document");
247            return;
248        }
249        boolean nodeprecated = options.noDeprecated();
250        performCopy(options.helpFile(), DocPath.empty);
251        performCopy(options.stylesheetFile(), DocPath.empty);
252        for (String stylesheet : options.additionalStylesheets()) {
253            performCopy(stylesheet, DocPath.empty);
254        }
255        for (String script : options.additionalScripts()) {
256            performCopy(script, DocPaths.SCRIPT_DIR);
257        }
258        // do early to reduce memory footprint
259        if (options.classUse()) {
260            ClassUseWriter.generate(configuration, classTree);
261        }
262
263        if (options.createTree()) {
264            TreeWriter.generate(configuration, classTree);
265        }
266
267        if (configuration.conditionalPages
268            .contains((HtmlConfiguration.ConditionalPage.DEPRECATED))) {
269            DeprecatedListWriter.generate(configuration);
270        }
271
272        if (configuration.conditionalPages
273            .contains((HtmlConfiguration.ConditionalPage.PREVIEW))) {
274            PreviewListWriter.generate(configuration);
275        }
276
277        if (configuration.conditionalPages
278            .contains((HtmlConfiguration.ConditionalPage.NEW))) {
279            NewAPIListWriter.generate(configuration);
280        }
281
282        if (options.createOverview()) {
283            if (configuration.showModules) {
284                ModuleIndexWriter.generate(configuration);
285            } else {
286                PackageIndexWriter.generate(configuration);
287            }
288        }
289
290        if (options.createIndex()) {
291            if (!options.noExternalSpecsPage()) {
292                ExternalSpecsWriter.generate(configuration);
293            }
294            SystemPropertiesWriter.generate(configuration);
295            configuration.mainIndex.addElements();
296            IndexBuilder allClassesIndex
297                = new IndexBuilder(configuration, nodeprecated, true);
298            allClassesIndex.addElements();
299            AllClassesIndexWriter.generate(configuration, allClassesIndex);
300            if (!configuration.packages.isEmpty()) {
301                AllPackagesIndexWriter.generate(configuration);
302            }
303            configuration.mainIndex.createSearchIndexFiles();
304            IndexWriter.generate(configuration);
305            SearchWriter.generate(configuration);
306        }
307
308        if (options.createOverview()) {
309            IndexRedirectWriter.generate(configuration,
310                DocPaths.OVERVIEW_SUMMARY, DocPaths.INDEX);
311        } else {
312            IndexRedirectWriter.generate(configuration);
313        }
314
315        if (options.helpFile().isEmpty() && !options.noHelp()) {
316            HelpWriter.generate(configuration);
317        }
318        // If a stylesheet file is not specified, copy the default stylesheet
319        // and replace newline with platform-specific newline.
320        DocFile f;
321        if (options.stylesheetFile().length() == 0) {
322            f = DocFile.createFileForOutput(configuration, DocPaths.STYLESHEET);
323            f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.STYLESHEET),
324                true, true);
325        }
326        f = DocFile.createFileForOutput(configuration, DocPaths.JAVASCRIPT);
327        f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.JAVASCRIPT), true,
328            true);
329        f = DocFile.createFileForOutput(configuration, DocPaths.CLIPBOARD_SVG);
330        f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.CLIPBOARD_SVG), true,
331            true);
332        f = DocFile.createFileForOutput(configuration, DocPaths.LINK_SVG);
333        f.copyResource(DocPaths.RESOURCES.resolve(DocPaths.LINK_SVG), true,
334            true);
335        if (options.createIndex()) {
336            f = DocFile.createFileForOutput(configuration, DocPaths.SEARCH_JS);
337            f.copyResource(
338                DOCLET_RESOURCES.resolve(DocPaths.SEARCH_JS_TEMPLATE),
339                configuration.docResources);
340
341            f = DocFile.createFileForOutput(configuration,
342                DocPaths.SEARCH_PAGE_JS);
343            f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.SEARCH_PAGE_JS),
344                configuration.docResources);
345
346            f = DocFile.createFileForOutput(configuration,
347                DocPaths.RESOURCES.resolve(DocPaths.GLASS_IMG));
348            f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.GLASS_IMG), true,
349                false);
350
351            f = DocFile.createFileForOutput(configuration,
352                DocPaths.RESOURCES.resolve(DocPaths.X_IMG));
353            f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.X_IMG), true,
354                false);
355            copyJqueryFiles();
356        }
357
358        copyLegalFiles(options.createIndex());
359    }
360
361    private void copyJqueryFiles() throws DocletException {
362        List<String> files = Arrays.asList(
363            DocPaths.JQUERY_JS.getPath(),
364            DocPaths.JQUERY_UI_JS.getPath(),
365            DocPaths.JQUERY_UI_CSS.getPath());
366        DocFile f;
367        for (String file : files) {
368            DocPath filePath = DocPaths.SCRIPT_DIR.resolve(file);
369            f = DocFile.createFileForOutput(configuration, filePath);
370            f.copyResource(DOCLET_RESOURCES.resolve(filePath), true, false);
371        }
372    }
373
374    private void copyLegalFiles(boolean includeJQuery) throws DocletException {
375        Path legalNoticesDir;
376        String legalNotices = configuration.getOptions().legalNotices();
377        switch (legalNotices) {
378        case "", "default" -> {
379            if (getClass().getModule().getName() == null) {
380                return;
381            }
382            Path javaHome = Path.of(System.getProperty("java.home"));
383            legalNoticesDir = javaHome.resolve("legal")
384                .resolve(getClass().getModule().getName());
385        }
386
387        case "none" -> {
388            return;
389        }
390
391        default -> {
392            try {
393                legalNoticesDir = Path.of(legalNotices);
394            } catch (InvalidPathException e) {
395                messages.error("doclet.Error_invalid_path_for_legal_notices",
396                    legalNotices, e.getMessage());
397                return;
398            }
399        }
400        }
401
402        if (Files.exists(legalNoticesDir)) {
403            try (DirectoryStream<Path> ds
404                = Files.newDirectoryStream(legalNoticesDir)) {
405                for (Path entry : ds) {
406                    if (!Files.isRegularFile(entry)) {
407                        continue;
408                    }
409                    if (entry.getFileName().toString().startsWith("jquery")
410                        && !includeJQuery) {
411                        continue;
412                    }
413                    DocPath filePath = DocPaths.LEGAL
414                        .resolve(entry.getFileName().toString());
415                    DocFile df
416                        = DocFile.createFileForOutput(configuration, filePath);
417                    df.copyFile(
418                        DocFile.createFileForInput(configuration, entry));
419                }
420            } catch (IOException e) {
421                messages.error("doclet.Error_copying_legal_notices", e);
422            }
423        }
424    }
425
426    @Override // defined by AbstractDoclet
427    protected void generateClassFiles(SortedSet<TypeElement> typeElems,
428            ClassTree classTree)
429            throws DocletException {
430        BuilderFactory f = configuration.getBuilderFactory();
431        for (TypeElement te : typeElems) {
432            if (utils.hasHiddenTag(te) ||
433                !(configuration.isGeneratedDoc(te) && utils.isIncluded(te))) {
434                continue;
435            }
436            f.getClassBuilder(te, classTree).build();
437        }
438    }
439
440    @Override // defined by AbstractDoclet
441    protected void generateModuleFiles() throws DocletException {
442        if (configuration.showModules) {
443            List<ModuleElement> mdles
444                = new ArrayList<>(configuration.modulePackages.keySet());
445            for (ModuleElement mdle : mdles) {
446                AbstractBuilder moduleSummaryBuilder = configuration
447                    .getBuilderFactory().getModuleSummaryBuilder(mdle);
448                moduleSummaryBuilder.build();
449            }
450        }
451    }
452
453    @Override // defined by AbstractDoclet
454    protected void generatePackageFiles(ClassTree classTree)
455            throws DocletException {
456        HtmlOptions options = configuration.getOptions();
457        Set<PackageElement> packages = configuration.packages;
458        List<PackageElement> pList = new ArrayList<>(packages);
459        for (PackageElement pkg : pList) {
460            // if -nodeprecated option is set and the package is marked as
461            // deprecated, do not generate the package-summary.html,
462            // package-frame.html
463            // and package-tree.html pages for that package.
464            if (!(options.noDeprecated() && utils.isDeprecated(pkg))) {
465                AbstractBuilder packageSummaryBuilder = configuration
466                    .getBuilderFactory().getPackageSummaryBuilder(pkg);
467                packageSummaryBuilder.build();
468                if (options.createTree()) {
469                    PackageTreeWriter.generate(configuration, pkg,
470                        options.noDeprecated());
471                }
472            }
473        }
474    }
475
476    @Override // defined by Doclet
477    public Set<? extends Option> getSupportedOptions() {
478        return configuration.getOptions().getSupportedOptions();
479    }
480
481    private void performCopy(String filename, DocPath targetPath)
482            throws DocFileIOException {
483        if (filename.isEmpty()) {
484            return;
485        }
486
487        DocFile fromfile = DocFile.createFileForInput(configuration, filename);
488        DocPath path = targetPath.resolve(fromfile.getName());
489        DocFile toFile = DocFile.createFileForOutput(configuration, path);
490        if (toFile.isSameFile(fromfile)) {
491            return;
492        }
493
494        messages.notice("doclet.Copying_File_0_To_File_1",
495            fromfile.getPath(), path.getPath());
496        toFile.copyFile(fromfile);
497    }
498}