View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.javadoc;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.Writer;
25  import java.net.MalformedURLException;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.net.URL;
29  import java.net.URLClassLoader;
30  import java.nio.charset.Charset;
31  import java.nio.charset.StandardCharsets;
32  import java.nio.file.Files;
33  import java.nio.file.Path;
34  import java.nio.file.StandardCopyOption;
35  import java.time.LocalDate;
36  import java.time.ZoneOffset;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.HashMap;
42  import java.util.HashSet;
43  import java.util.LinkedHashMap;
44  import java.util.LinkedHashSet;
45  import java.util.LinkedList;
46  import java.util.List;
47  import java.util.Locale;
48  import java.util.Map;
49  import java.util.Map.Entry;
50  import java.util.Optional;
51  import java.util.Properties;
52  import java.util.Set;
53  import java.util.StringTokenizer;
54  import java.util.regex.Pattern;
55  import java.util.stream.Collectors;
56  
57  import org.apache.commons.lang3.BooleanUtils;
58  import org.apache.commons.lang3.StringUtils;
59  import org.apache.maven.RepositoryUtils;
60  import org.apache.maven.archiver.MavenArchiver;
61  import org.apache.maven.artifact.Artifact;
62  import org.apache.maven.artifact.ArtifactUtils;
63  import org.apache.maven.artifact.handler.ArtifactHandler;
64  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
65  import org.apache.maven.artifact.versioning.ArtifactVersion;
66  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
67  import org.apache.maven.doxia.tools.SiteTool;
68  import org.apache.maven.execution.MavenSession;
69  import org.apache.maven.model.Dependency;
70  import org.apache.maven.model.Plugin;
71  import org.apache.maven.model.Resource;
72  import org.apache.maven.plugin.AbstractMojo;
73  import org.apache.maven.plugin.MojoExecution;
74  import org.apache.maven.plugin.MojoExecutionException;
75  import org.apache.maven.plugin.MojoFailureException;
76  import org.apache.maven.plugins.annotations.Parameter;
77  import org.apache.maven.plugins.javadoc.options.BootclasspathArtifact;
78  import org.apache.maven.plugins.javadoc.options.DocletArtifact;
79  import org.apache.maven.plugins.javadoc.options.Group;
80  import org.apache.maven.plugins.javadoc.options.JavadocOptions;
81  import org.apache.maven.plugins.javadoc.options.JavadocPathArtifact;
82  import org.apache.maven.plugins.javadoc.options.OfflineLink;
83  import org.apache.maven.plugins.javadoc.options.ResourcesArtifact;
84  import org.apache.maven.plugins.javadoc.options.Tag;
85  import org.apache.maven.plugins.javadoc.options.Taglet;
86  import org.apache.maven.plugins.javadoc.options.TagletArtifact;
87  import org.apache.maven.plugins.javadoc.options.io.xpp3.JavadocOptionsXpp3Writer;
88  import org.apache.maven.plugins.javadoc.resolver.JavadocBundle;
89  import org.apache.maven.plugins.javadoc.resolver.ResourceResolver;
90  import org.apache.maven.plugins.javadoc.resolver.SourceResolverConfig;
91  import org.apache.maven.project.DefaultProjectBuildingRequest;
92  import org.apache.maven.project.MavenProject;
93  import org.apache.maven.project.ProjectBuilder;
94  import org.apache.maven.project.ProjectBuildingException;
95  import org.apache.maven.project.ProjectBuildingRequest;
96  import org.apache.maven.reporting.MavenReportException;
97  import org.apache.maven.settings.Proxy;
98  import org.apache.maven.settings.Settings;
99  import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
100 import org.apache.maven.shared.artifact.filter.resolve.PatternExclusionsFilter;
101 import org.apache.maven.shared.artifact.filter.resolve.PatternInclusionsFilter;
102 import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
103 import org.apache.maven.shared.invoker.MavenInvocationException;
104 import org.apache.maven.toolchain.Toolchain;
105 import org.apache.maven.toolchain.ToolchainManager;
106 import org.apache.maven.wagon.PathUtils;
107 import org.codehaus.plexus.archiver.ArchiverException;
108 import org.codehaus.plexus.archiver.UnArchiver;
109 import org.codehaus.plexus.archiver.manager.ArchiverManager;
110 import org.codehaus.plexus.archiver.manager.NoSuchArchiverException;
111 import org.codehaus.plexus.components.io.fileselectors.IncludeExcludeFileSelector;
112 import org.codehaus.plexus.languages.java.jpms.JavaModuleDescriptor;
113 import org.codehaus.plexus.languages.java.jpms.LocationManager;
114 import org.codehaus.plexus.languages.java.jpms.ModuleNameSource;
115 import org.codehaus.plexus.languages.java.jpms.ResolvePathRequest;
116 import org.codehaus.plexus.languages.java.jpms.ResolvePathResult;
117 import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
118 import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
119 import org.codehaus.plexus.languages.java.version.JavaVersion;
120 import org.codehaus.plexus.util.FileUtils;
121 import org.codehaus.plexus.util.WriterFactory;
122 import org.codehaus.plexus.util.cli.CommandLineException;
123 import org.codehaus.plexus.util.cli.CommandLineUtils;
124 import org.codehaus.plexus.util.cli.Commandline;
125 import org.codehaus.plexus.util.xml.Xpp3Dom;
126 import org.eclipse.aether.RepositorySystem;
127 import org.eclipse.aether.RepositorySystemSession;
128 import org.eclipse.aether.artifact.ArtifactTypeRegistry;
129 import org.eclipse.aether.artifact.DefaultArtifact;
130 import org.eclipse.aether.collection.CollectRequest;
131 import org.eclipse.aether.graph.DependencyFilter;
132 import org.eclipse.aether.resolution.ArtifactRequest;
133 import org.eclipse.aether.resolution.ArtifactResolutionException;
134 import org.eclipse.aether.resolution.ArtifactResult;
135 import org.eclipse.aether.resolution.DependencyRequest;
136 import org.eclipse.aether.resolution.DependencyResolutionException;
137 import org.eclipse.aether.util.filter.AndDependencyFilter;
138 import org.eclipse.aether.util.filter.PatternExclusionsDependencyFilter;
139 import org.eclipse.aether.util.filter.ScopeDependencyFilter;
140 
141 import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
142 import static org.apache.maven.plugins.javadoc.JavadocUtil.isEmpty;
143 import static org.apache.maven.plugins.javadoc.JavadocUtil.isNotEmpty;
144 import static org.apache.maven.plugins.javadoc.JavadocUtil.toList;
145 
146 /**
147  * Base class with the majority of Javadoc functionality.
148  *
149  * @author <a href="mailto:brett@apache.org">Brett Porter</a>
150  * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
151  * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html">The javadoc Command</a>
152  * @since 2.0
153  */
154 public abstract class AbstractJavadocMojo extends AbstractMojo {
155     /**
156      * Classifier used in the name of the javadoc-options XML file, and in the resources bundle
157      * artifact that gets attached to the project. This one is used for non-test javadocs.
158      *
159      * @see #TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER
160      * @since 2.7
161      */
162     public static final String JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER = "javadoc-resources";
163 
164     /**
165      * Classifier used in the name of the javadoc-options XML file, and in the resources bundle
166      * artifact that gets attached to the project. This one is used for test-javadocs.
167      *
168      * @see #JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER
169      * @since 2.7
170      */
171     public static final String TEST_JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER = "test-javadoc-resources";
172 
173     /**
174      * The Javadoc script file name when <code>debug</code> parameter is on, i.e. javadoc.bat or javadoc.sh
175      */
176     protected static final String DEBUG_JAVADOC_SCRIPT_NAME = "javadoc." + (SystemUtils.IS_OS_WINDOWS ? "bat" : "sh");
177 
178     /**
179      * The <code>options</code> file name in the output directory when calling:
180      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
181      */
182     protected static final String OPTIONS_FILE_NAME = "options";
183 
184     /**
185      * The <code>packages</code> file name in the output directory when calling:
186      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
187      */
188     protected static final String PACKAGES_FILE_NAME = "packages";
189 
190     /**
191      * The <code>argfile</code> file name in the output directory when calling:
192      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
193      */
194     protected static final String ARGFILE_FILE_NAME = "argfile";
195 
196     /**
197      * The <code>files</code> file name in the output directory when calling:
198      * <code>javadoc.exe(or .sh) &#x40;options &#x40;packages | &#x40;argfile | &#x40;files</code>
199      */
200     protected static final String FILES_FILE_NAME = "files";
201 
202     /**
203      * Default CSS file name, used as file name in the output directory for the temporary custom stylesheet file
204      * loaded from classloader resources.
205      */
206     private static final String DEFAULT_CSS_NAME = "stylesheet.css";
207 
208     private static final String PACKAGE_LIST = "package-list";
209     private static final String ELEMENT_LIST = "element-list";
210 
211     /**
212      * For Javadoc options appears since Java 8.0.
213      * See <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/index.html">
214      * Javadoc Technology</a>
215      *
216      * @since 3.0.0
217      */
218     private static final JavaVersion SINCE_JAVADOC_1_8 = JavaVersion.parse("1.8");
219 
220     private static final JavaVersion JAVA_VERSION = JavaVersion.JAVA_SPECIFICATION_VERSION;
221 
222     // ----------------------------------------------------------------------
223     // Mojo components
224     // ----------------------------------------------------------------------
225 
226     /**
227      * SiteTool.
228      */
229     protected SiteTool siteTool;
230 
231     /**
232      * Archiver manager
233      *
234      * @since 2.5
235      */
236     private ArchiverManager archiverManager;
237 
238     private ResourceResolver resourceResolver;
239 
240     private RepositorySystem repoSystem;
241 
242     @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true)
243     private RepositorySystemSession repoSession;
244 
245     private ArtifactHandlerManager artifactHandlerManager;
246 
247     /**
248      * Project builder
249      *
250      * @since 3.0
251      */
252     private ProjectBuilder mavenProjectBuilder;
253 
254     private ToolchainManager toolchainManager;
255 
256     public AbstractJavadocMojo(
257             SiteTool siteTool,
258             ArchiverManager archiverManager,
259             ResourceResolver resourceResolver,
260             RepositorySystem repoSystem,
261             ArtifactHandlerManager artifactHandlerManager,
262             ProjectBuilder mavenProjectBuilder,
263             ToolchainManager toolchainManager) {
264         this.siteTool = siteTool;
265         this.archiverManager = archiverManager;
266         this.resourceResolver = resourceResolver;
267         this.repoSystem = repoSystem;
268         this.artifactHandlerManager = artifactHandlerManager;
269         this.mavenProjectBuilder = mavenProjectBuilder;
270         this.toolchainManager = toolchainManager;
271     }
272 
273     final LocationManager locationManager = new LocationManager();
274 
275     // ----------------------------------------------------------------------
276     // Mojo parameters
277     // ----------------------------------------------------------------------
278 
279     /**
280      * The current build session instance. This is used for
281      * toolchain manager API calls.
282      */
283     @Parameter(defaultValue = "${session}", readonly = true, required = true)
284     protected MavenSession session;
285 
286     /**
287      * The Maven Settings.
288      *
289      * @since 2.3
290      */
291     @Parameter(defaultValue = "${settings}", readonly = true, required = true)
292     private Settings settings;
293 
294     /**
295      * The Maven Project Object
296      */
297     @Parameter(defaultValue = "${project}", readonly = true, required = true)
298     protected MavenProject project;
299 
300     @Parameter(defaultValue = "${mojoExecution}", readonly = true, required = true)
301     protected MojoExecution mojoExecution;
302 
303     /**
304      * Specify if the Javadoc plugin should operate in offline mode. If maven is run in offline
305      * mode (using {@code -o} or {@code --offline} on the command line), this option has no effect
306      * and the plugin is always in offline mode.
307      *
308      * @since 3.6.0
309      */
310     @Parameter(property = "maven.javadoc.offline", defaultValue = "false")
311     private boolean offline;
312 
313     /**
314      * Specifies the Javadoc resources directory to be included in the Javadoc (i.e. package.html, images...).
315      * <br/>
316      * Could be used in addition of <code>docfilessubdirs</code> parameter.
317      * <br/>
318      * See <a href="#docfilessubdirs">docfilessubdirs</a>.
319      *
320      * @see #docfilessubdirs
321      * @since 2.1
322      */
323     @Parameter(defaultValue = "${basedir}/src/main/javadoc")
324     private File javadocDirectory;
325 
326     /**
327      * Set an additional option(s) on the command line. All input will be passed as-is to the
328      * {@code @options} file. You must take care of quoting and escaping. Useful for a custom doclet.
329      *
330      * @since 3.0.0
331      */
332     @Parameter
333     private String[] additionalOptions;
334 
335     /**
336      * Sets additional Javadoc options (e.g. JVM options) on the command line.
337      * Example:
338      * <pre>
339      * &lt;additionalJOption&gt;-J-Xss128m&lt;/additionalJOption&gt;
340      * </pre>
341      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">
342      * Javadoc Options</a>
343      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/java.html#overview-of-java-options">
344      * VM Options</a>
345      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/net/doc-files/net-properties.html">
346      * Networking Properties</a>
347      *
348      * @since 2.3
349      */
350     @Parameter(property = "additionalJOption")
351     private String additionalJOption;
352 
353     /**
354      * Sets additional Javadoc options for the execution of the javadoc command via the '-J' option to javadoc.
355      * Example:
356      * <pre>
357      *     &lt;additionalJOptions&gt;
358      *         &lt;additionalJOption&gt;-J-Xmx1g &lt;/additionalJOption&gt;
359      *     &lt;/additionalJOptions&gt;
360      * </pre>
361      * @since 2.9
362      * @see #additionalJOption
363      */
364     @Parameter
365     private String[] additionalJOptions;
366 
367     /**
368      * A list of artifacts containing resources which should be copied into the
369      * Javadoc output directory (like stylesheets, icons, etc.).
370      * <br/>
371      * Example:
372      * <pre>
373      * &lt;resourcesArtifacts&gt;
374      *   &lt;resourcesArtifact&gt;
375      *     &lt;groupId&gt;external.group.id&lt;/groupId&gt;
376      *     &lt;artifactId&gt;external-resources&lt;/artifactId&gt;
377      *     &lt;version&gt;1.0&lt;/version&gt;
378      *   &lt;/resourcesArtifact&gt;
379      * &lt;/resourcesArtifacts&gt;
380      * </pre>
381      * <br/>
382      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/ResourcesArtifact.html">Javadoc</a>.
383      * <br/>
384      *
385      * @since 2.5
386      */
387     @Parameter(property = "resourcesArtifacts")
388     private ResourcesArtifact[] resourcesArtifacts;
389 
390     /**
391      * The projects in the reactor for aggregation report.
392      */
393     @Parameter(defaultValue = "${reactorProjects}", required = true, readonly = true)
394     protected List<MavenProject> reactorProjects;
395 
396     /**
397      * Set this to <code>true</code> to debug the Javadoc plugin. With this, <code>javadoc.bat(or.sh)</code>,
398      * <code>options</code>, <code>@packages</code> or <code>argfile</code> files are provided in the output directory.
399      * <br/>
400      *
401      * @since 2.1
402      */
403     @Parameter(property = "debug", defaultValue = "false")
404     private boolean debug;
405 
406     /**
407      * Sets the absolute path of the Javadoc Tool executable to use. Since version 2.5, a mere directory specification
408      * is sufficient to have the plugin use "javadoc" or "javadoc.exe" respectively from this directory.
409      *
410      * @since 2.3
411      */
412     @Parameter(property = "javadocExecutable")
413     private String javadocExecutable;
414 
415     /**
416      * Version of the Javadoc Tool executable to use, ex. "1.3", "1.5".
417      *
418      * @since 2.3
419      */
420     @Parameter(property = "javadocVersion")
421     private String javadocVersion;
422 
423     /**
424      * Version of the Javadoc Tool executable to use.
425      */
426     private JavaVersion javadocRuntimeVersion;
427 
428     /**
429      * Specifies whether the Javadoc generation should be skipped.
430      *
431      * @since 2.5
432      */
433     @Parameter(property = "maven.javadoc.skip", defaultValue = "false")
434     protected boolean skip;
435 
436     /**
437      * Specifies if the build will fail if there are errors during javadoc execution or not.
438      *
439      * @since 2.5
440      */
441     @Parameter(property = "maven.javadoc.failOnError", defaultValue = "true")
442     protected boolean failOnError;
443 
444     /**
445      * Specifies if the build will fail if there are warning during javadoc execution or not.
446      *
447      * @since 3.0.1
448      */
449     @Parameter(property = "maven.javadoc.failOnWarnings", defaultValue = "false")
450     protected boolean failOnWarnings;
451 
452     /**
453      * Specifies to use the
454      * <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">
455      * options provided by the Standard Doclet</a> for a custom doclet.
456      * <br>
457      * Example:
458      * <pre>
459      * &lt;docletArtifacts&gt;
460      *   &lt;docletArtifact&gt;
461      *     &lt;groupId&gt;com.sun.tools.doclets&lt;/groupId&gt;
462      *     &lt;artifactId&gt;doccheck&lt;/artifactId&gt;
463      *     &lt;version&gt;1.2b2&lt;/version&gt;
464      *   &lt;/docletArtifact&gt;
465      * &lt;/docletArtifacts&gt;
466      * &lt;useStandardDocletOptions&gt;true&lt;/useStandardDocletOptions&gt;
467      * </pre>
468      *
469      * @since 2.5
470      */
471     @Parameter(property = "useStandardDocletOptions", defaultValue = "true")
472     protected boolean useStandardDocletOptions;
473 
474     /**
475      * Detect the Javadoc links for all dependencies defined in the project. The detection is based on the default
476      * Maven conventions, i.e.: <code>${project.url}/apidocs</code>.
477      * <br/>
478      * For instance, if the project has a dependency to
479      * <a href="http://commons.apache.org/lang/">Apache Commons Lang</a> i.e.:
480      * <pre>
481      * &lt;dependency&gt;
482      *   &lt;groupId&gt;commons-lang&lt;/groupId&gt;
483      *   &lt;artifactId&gt;commons-lang&lt;/artifactId&gt;
484      * &lt;/dependency&gt;
485      * </pre>
486      * The added Javadoc <code>-link</code> parameter will be <code>http://commons.apache.org/lang/apidocs</code>.
487      *
488      * @see #links
489      * @see #dependencyLinks
490      * @since 2.6
491      */
492     @Parameter(property = "detectLinks", defaultValue = "false")
493     private boolean detectLinks;
494 
495     /**
496      * Detect the links for all modules defined in the project.
497      * <br/>
498      * If {@code reactorProjects} is defined in a non-aggregator way, it generates default offline links
499      * between modules based on the defined project's URLs. For instance, if a parent project has two projects
500      * <code>module1</code> and <code>module2</code>, the <code>-linkoffline</code> will be:
501      * <br/>
502      * The added Javadoc <code>-linkoffline</code> parameter for <b>module1</b> will be
503      * <code>/absolute/path/to/</code><b>module2</b><code>/target/site/apidocs</code>
504      * <br/>
505      * The added Javadoc <code>-linkoffline</code> parameter for <b>module2</b> will be
506      * <code>/absolute/path/to/</code><b>module1</b><code>/target/site/apidocs</code>
507      *
508      * @see #offlineLinks
509      * @since 2.6
510      */
511     @Parameter(property = "detectOfflineLinks", defaultValue = "true")
512     private boolean detectOfflineLinks;
513 
514     /**
515      * Detect the Java API link for the current build, e.g. <code>https://docs.oracle.com/javase/1.4.2/docs/api/</code>
516      * for Java source 1.4.
517      * <br/>
518      * By default, the goal detects the Javadoc API link depending on the value of the <code>source</code>
519      * parameter in the <code>org.apache.maven.plugins:maven-compiler-plugin</code>
520      * (defined in <code>${project.build.plugins}</code> or in <code>${project.build.pluginManagement}</code>),
521      * or try to compute it from the {@code javadocExecutable} version.
522      *
523      * @see #links
524      * @see #javaApiLinks
525      * @since 2.6
526      */
527     @Parameter(property = "detectJavaApiLink", defaultValue = "true")
528     private boolean detectJavaApiLink;
529 
530     /**
531      * Use this parameter <b>only</b> if you want to override the default URLs.
532      *
533      * The key should match {@code api_x}, where {@code x} matches the Java version.
534      *
535      *  For example:
536      *  <dl>
537      *   <dt>api_1.5</dt>
538      *   <dd>https://docs.oracle.com/javase/1.5.0/docs/api/</dd>
539      *   <dt>api_1.8<dt>
540      *   <dd>https://docs.oracle.com/javase/8/docs/api/</dd>
541      *   <dt>api_9</dd>
542      *   <dd>https://docs.oracle.com/javase/9/docs/api/</dd>
543      * </dl>
544      * @since 2.6
545      */
546     @Parameter(property = "javaApiLinks")
547     private Properties javaApiLinks;
548 
549     /**
550      * Flag controlling content validation of <code>package-list</code>/<code>element-list</code> resources.
551      * If set, the content of <code>package-list</code>/<code>element-list</code> resources will be validated.
552      *
553      * @since 2.8
554      */
555     @Parameter(property = "validateLinks", defaultValue = "false")
556     private boolean validateLinks;
557 
558     // ----------------------------------------------------------------------
559     // Javadoc Options - all alphabetical
560     // ----------------------------------------------------------------------
561 
562     /**
563      * Specifies the paths where the boot classes reside. The <code>bootclasspath</code> can contain multiple paths
564      * by separating them with a colon (<code>:</code>) or a semicolon (<code>;</code>).
565      * @see <a href=
566      *    "https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-boot-class-path">
567      *    Javadoc option bootclasspath</a>.
568      * @since 2.5
569      */
570     @Parameter(property = "bootclasspath")
571     private String bootclasspath;
572 
573     /**
574      * Specifies the artifacts where the boot classes reside.
575      * <br/>
576      * Example:
577      * <pre>
578      * &lt;bootclasspathArtifacts&gt;
579      *   &lt;bootclasspathArtifact&gt;
580      *     &lt;groupId&gt;my-groupId&lt;/groupId&gt;
581      *     &lt;artifactId&gt;my-artifactId&lt;/artifactId&gt;
582      *     &lt;version&gt;my-version&lt;/version&gt;
583      *   &lt;/bootclasspathArtifact&gt;
584      * &lt;/bootclasspathArtifacts&gt;
585      * </pre>
586      * <br/>
587      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/BootclasspathArtifact.html">Javadoc</a>.
588      *
589      * @see <a href=
590      *   "https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-boot-class-path">
591      *   Javadoc option bootclasspath</a>
592      * @since 2.5
593      */
594     @Parameter(property = "bootclasspathArtifacts")
595     private BootclasspathArtifact[] bootclasspathArtifacts;
596 
597     /**
598      * Uses the sentence break iterator to determine the end of the first sentence.
599      * @see <a href=
600      * "https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">
601      * Javadoc option breakiterator</a>.
602      */
603     @Parameter(property = "breakiterator", defaultValue = "false")
604     private boolean breakiterator;
605 
606     /**
607      * Specifies the class file that starts the doclet used in generating the documentation.
608      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option doclet</a>.
609      */
610     @Parameter(property = "doclet")
611     private String doclet;
612 
613     /**
614      * Specifies the artifact containing the doclet starting class file (specified with the {@link #doclet}
615      * option).
616      * <br/>
617      * Example:
618      * <pre>
619      * &lt;docletArtifact&gt;
620      *   &lt;groupId&gt;com.sun.tools.doclets&lt;/groupId&gt;
621      *   &lt;artifactId&gt;doccheck&lt;/artifactId&gt;
622      *   &lt;version&gt;1.2b2&lt;/version&gt;
623      * &lt;/docletArtifact&gt;
624      * </pre>
625      * <br/>
626      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/DocletArtifact.html">Javadoc</a>.
627      * <br/>
628      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option docletpath</a>.
629      */
630     @Parameter(property = "docletArtifact")
631     private DocletArtifact docletArtifact;
632 
633     /**
634      * Specifies multiple artifacts containing the path for the doclet starting class file (specified with the
635      * {@link #doclet} option).
636      * <br/>
637      * Example:
638      * <pre>
639      * &lt;docletArtifacts&gt;
640      *   &lt;docletArtifact&gt;
641      *     &lt;groupId&gt;com.sun.tools.doclets&lt;/groupId&gt;
642      *     &lt;artifactId&gt;doccheck&lt;/artifactId&gt;
643      *     &lt;version&gt;1.2b2&lt;/version&gt;
644      *   &lt;/docletArtifact&gt;
645      * &lt;/docletArtifacts&gt;
646      * </pre>
647      * <br/>
648      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/DocletArtifact.html">Javadoc</a>.
649      * <br/>
650      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option docletpath</a>.
651      * @since 2.1
652      */
653     @Parameter(property = "docletArtifacts")
654     private DocletArtifact[] docletArtifacts;
655 
656     /**
657      * Specifies the path to the doclet starting class file (specified with the {@link #doclet} option) and
658      * any jar files it depends on. The <code>docletPath</code> can contain multiple paths by separating them with
659      * a colon (<code>:</code>) or a semicolon (<code>;</code>).
660      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option docletpath</a>.
661      */
662     @Parameter(property = "docletPath")
663     private String docletPath;
664 
665     /**
666      * Specifies the encoding name of the source files. If not specified, the encoding value will be the value of the
667      * <code>file.encoding</code> system property.
668      * <br/>
669      * <b>Note</b>: In 2.4, the default value was locked to <code>ISO-8859-1</code> to ensure reproducing build, but
670      * this was reverted in 2.5.
671      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-encoding">Javadoc option encoding</a>.
672      */
673     @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
674     private String encoding;
675 
676     /**
677      * Unconditionally excludes the specified packages and their subpackages from the list formed by
678      * <code>-subpackages</code>. Multiple packages can be separated by commas (<code>,</code>), colons (<code>:</code>)
679      * or semicolons (<code>;</code>).
680      * <p>
681      * Wildcards work as followed:
682      * <ul>
683      *   <li>a wildcard at the beginning should match one or more directories</li>
684      *   <li>any other wildcard must match exactly one directory</li>
685      * </ul>
686      * </p>
687      * Example:
688      * <pre>
689      * &lt;excludePackageNames&gt;*.internal:org.acme.exclude1.*:org.acme.exclude2&lt;/excludePackageNames&gt;
690      * </pre>
691      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option exclude</a>.
692      */
693     @Parameter(property = "excludePackageNames")
694     private String excludePackageNames;
695 
696     /**
697      * Specifies the directories where extension classes reside. Separate directories in <code>extdirs</code> with a
698      * colon (<code>:</code>) or a semicolon (<code>;</code>).
699      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-extdirs">Javadoc option extdirs</a>.
700      */
701     @Parameter(property = "extdirs")
702     private String extdirs;
703 
704     /**
705      * Specifies the locale that javadoc uses when generating documentation.
706      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option locale</a>.
707      */
708     @Parameter(property = "locale")
709     private String locale;
710 
711     /**
712      * Specifies the maximum Java heap size to be used when launching the Javadoc tool.
713      * JVMs refer to this property as the <code>-Xmx</code> parameter. Example: '512' or '512m'.
714      * The memory unit depends on the JVM used. The units supported could be: <code>k</code>, <code>kb</code>,
715      * <code>m</code>, <code>mb</code>, <code>g</code>, <code>gb</code>, <code>t</code>, <code>tb</code>.
716      * If no unit specified, the default unit is <code>m</code>.
717      */
718     @Parameter(property = "maxmemory")
719     private String maxmemory;
720 
721     /**
722      * Specifies the minimum Java heap size to be used when launching the Javadoc tool.
723      * JVMs refer to this property as the <code>-Xms</code> parameter. Example: '512' or '512m'.
724      * The memory unit depends on the JVM used. The units supported could be: <code>k</code>, <code>kb</code>,
725      * <code>m</code>, <code>mb</code>, <code>g</code>, <code>gb</code>, <code>t</code>, <code>tb</code>.
726      * If no unit specified, the default unit is <code>m</code>.
727      */
728     @Parameter(property = "minmemory")
729     private String minmemory;
730 
731     /**
732      * This option creates documentation with the appearance and functionality of documentation generated by
733      * Javadoc 1.1. This is no longer supported since Javadoc 1.4 (shipped with JDK 1.4)
734      *
735      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#a1.1">Javadoc option 1.1</a>.
736      * @deprecated No longer used.
737      */
738     @Parameter(property = "old", defaultValue = "false")
739     @Deprecated
740     private boolean old;
741 
742     /**
743      * Specifies that javadoc should retrieve the text for the overview documentation from the "source" file
744      * specified by path/filename and place it on the Overview page (overview-summary.html).
745      * <br/>
746      * <b>Note</b>: could be in conflict with {@link #nooverview}.
747      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Javadoc option overview</a>.
748      * <br/>
749      */
750     @Parameter(property = "overview", defaultValue = "${basedir}/src/main/javadoc/overview.html")
751     private File overview;
752 
753     /**
754      * Shuts off non-error and non-warning messages, leaving only the warnings and errors appear, making them
755      * easier to view.
756      * <br/>
757      * Note: was a standard doclet in Java 1.4.2 (refer to bug ID
758      * <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4714350">4714350</a>).
759      * <br/>
760      * Since Java 5.0.
761      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option quiet</a>.
762      */
763     @Parameter(property = "quiet", defaultValue = "false")
764     private boolean quiet;
765 
766     /**
767      * Specifies the access level for classes and members to show in the Javadocs.
768      * Possible values are:
769      * <ul>
770      * <li>public (shows only public classes and members)</li>
771      * <li>protected (shows only public and protected classes and members)</li>
772      * <li>package (shows all classes and members not marked private)</li>
773      * <li>private (shows all classes and members)</li>
774      * </ul>
775      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc options private, protected, public and package</a>
776      */
777     @Parameter(property = "show", defaultValue = "protected")
778     private String show;
779 
780     /**
781      * Provide source compatibility with specified release. Since JDK 9 rather use {@link #release}.
782      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-source">Javadoc option source</a>.
783      */
784     @Parameter(property = "source", defaultValue = "${maven.compiler.source}")
785     private String source;
786 
787     /**
788      * Provide source compatibility with specified release
789      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-release">Javadoc option release</a>.
790      * @since JDK 9
791      * @since 3.1.0
792      */
793     @Parameter(defaultValue = "${maven.compiler.release}")
794     private String release;
795 
796     /**
797      * Specifies the source paths where the subpackages are located. The <code>sourcepath</code> can contain
798      * multiple paths by separating them with a colon (<code>:</code>) or a semicolon (<code>;</code>).
799      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html#option-source-path">Javadoc option sourcepath</a>.
800      */
801     @Parameter(property = "sourcepath")
802     private String sourcepath;
803 
804     /**
805      * When using legacyMode and aggregated javadoc, users may have a mix of Maven modules with module files and not.
806      * The javadoc need to be called with empty {@code -sourcepath} argument and files are in the argfile
807      * This usually need to be used with the following configuration
808      * <pre>
809      * {@code
810      *   <sourceFileExcludes>
811      *     <sourceFileExclude>**\/module-info.java</sourceFileExclude>
812      *   </sourceFileExcludes>
813      * }
814      * </pre>
815      * @since 3.11.2
816      */
817     @Parameter(property = "maven.javadoc.disableSourcepathUsage")
818     private boolean disableSourcepathUsage;
819 
820     /**
821      * Specifies the package directory where javadoc will be executed. Multiple packages can be separated by
822      * colons (<code>:</code>).
823      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option subpackages</a>.
824      */
825     @Parameter(property = "subpackages")
826     private String subpackages;
827 
828     /**
829      * Provides more detailed messages while javadoc is running.
830      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#options-for-javadoc">Javadoc option verbose</a>.
831      */
832     @Parameter(property = "verbose", defaultValue = "false")
833     private boolean verbose;
834 
835     /**
836      * Run the javadoc tool in pre-Java 9 (non-modular) style even if the java version is
837      * post java 9. This allows non-JPMS projects that have moved to newer Java
838      * versions to create javadocs without having to use JPMS modules.
839      *
840      * @since 3.6.0
841      */
842     @Parameter(property = "legacyMode", defaultValue = "false")
843     private boolean legacyMode;
844 
845     // ----------------------------------------------------------------------
846     // Standard Doclet Options - all alphabetical
847     // ----------------------------------------------------------------------
848 
849     /**
850      * Specifies whether the author text is included in the generated Javadocs.
851      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option author</a>.
852      */
853     @Parameter(property = "author", defaultValue = "true")
854     private boolean author;
855 
856     /**
857      * Specifies the text to be placed at the bottom of each output file.<br/>
858      * If you want to use html, you have to put it in a CDATA section, <br/>
859      * e.g. <code>&lt;![CDATA[Copyright 2005, &lt;a href="http://www.mycompany.com">MyCompany, Inc.&lt;a>]]&gt;</code>
860      * <br>
861      * <strong>Note:<strong>If the project has the property <code>project.build.outputTimestamp</code>, its year will
862      * be used as {currentYear}. This way it is possible to generate reproducible javadoc jars.
863      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option bottom</a>.
864      */
865     @Parameter(
866             property = "bottom",
867             defaultValue = "Copyright &#169; {inceptionYear}&#x2013;{currentYear} {organizationName}. "
868                     + "All rights reserved.")
869     private String bottom;
870 
871     /**
872      * Specifies the HTML character set for this document. If not specified, the charset value will be the value of
873      * the {@link #docencoding} parameter.
874      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option charset</a>.
875      */
876     @Parameter(property = "charset")
877     private String charset;
878 
879     /**
880      * Specifies the encoding of the generated HTML files. If not specified, the docencoding value will be
881      * <code>UTF-8</code>.
882      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option docencoding</a>.
883      */
884     @Parameter(property = "docencoding", defaultValue = "${project.reporting.outputEncoding}")
885     private String docencoding;
886 
887     /**
888      * Enables deep copying of the <code>&#42;&#42;/doc-files</code> directories and the specific <code>resources</code>
889      * directory from the <code>javadocDirectory</code> directory (for instance,
890      * <code>src/main/javadoc/com/mycompany/myapp/doc-files</code> and <code>src/main/javadoc/resources</code>).
891      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option docfilessubdirs</a>.
892      * @see #excludedocfilessubdir
893      * @see #javadocDirectory
894      */
895     @Parameter(property = "docfilessubdirs", defaultValue = "false")
896     private boolean docfilessubdirs;
897 
898     /**
899      * Specifies specific checks to be performed on Javadoc comments.
900      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#additional-options-provided-by-the-standard-doclet">Additional Doclet option Xdoclint</a>.
901      *
902      * @since 3.0.0
903      */
904     @Parameter(property = "doclint")
905     private String doclint;
906 
907     /**
908      * Specifies the title to be placed near the top of the overview summary file.
909      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option doctitle</a>.
910      */
911     @Parameter(property = "doctitle", defaultValue = "${project.name} ${project.version} API")
912     private String doctitle;
913 
914     /**
915      * Excludes any "doc-files" subdirectories with the given names. Multiple patterns can be excluded
916      * by separating them with colons (<code>:</code>).
917      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option excludedocfilessubdir</a>.
918      * @see #docfilessubdirs
919      */
920     @Parameter(property = "excludedocfilessubdir")
921     private String excludedocfilessubdir;
922 
923     /**
924      * Specifies the footer text to be placed at the bottom of each output file.
925      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option footer</a>.
926      */
927     @Parameter(property = "footer")
928     private String footer;
929 
930     /**
931      * Separates packages on the overview page into whatever groups you specify, one group per table. The
932      * packages pattern can be any package name, or can be the start of any package name followed by an asterisk
933      * (<code>*</code>) meaning "match any characters". Multiple patterns can be included in a group
934      * by separating them with colons (<code>:</code>).
935      * <br/>
936      * Example:
937      * <pre>
938      * &lt;groups&gt;
939      *   &lt;group&gt;
940      *     &lt;title&gt;Core Packages&lt;/title&gt;
941      *     &lt;!-- To includes java.lang, java.lang.ref,
942      *     java.lang.reflect and only java.util
943      *     (i.e. not java.util.jar) --&gt;
944      *     &lt;packages&gt;java.lang*:java.util&lt;/packages&gt;
945      *   &lt;/group&gt;
946      *   &lt;group&gt;
947      *     &lt;title&gt;Extension Packages&lt;/title&gt;
948      *     &nbsp;&lt;!-- To include javax.accessibility,
949      *     javax.crypto, ... (among others) --&gt;
950      *     &lt;packages&gt;javax.*&lt;/packages&gt;
951      *   &lt;/group&gt;
952      * &lt;/groups&gt;
953      * </pre>
954      * <b>Note</b>: using <code>java.lang.*</code> for <code>packages</code> would omit the <code>java.lang</code>
955      * package but using <code>java.lang*</code> will include it.
956      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option group</a>.
957      * @see Group
958      */
959     @Parameter
960     private Group[] groups;
961 
962     /**
963      * Specifies the header text to be placed at the top of each output file.
964      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option header</a>.
965      */
966     @Parameter(property = "header")
967     private String header;
968 
969     /**
970      * Specifies the path of an alternate help file path\filename that the HELP link in the top and bottom
971      * navigation bars link to.
972      * <br/>
973      * <b>Note</b>: could be in conflict with &lt;nohelp/&gt;.
974      * <br/>
975      * The <code>helpfile</code> could be an absolute File path.
976      * <br/>
977      * Since 2.6, it could be also be a path from a resource in the current project source directories
978      * (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
979      * or from a resource in the Javadoc plugin dependencies, for instance:
980      * <pre>
981      * &lt;helpfile&gt;path/to/your/resource/yourhelp-doc.html&lt;/helpfile&gt;
982      * </pre>
983      * Where <code>path/to/your/resource/yourhelp-doc.html</code> could be in <code>src/main/javadoc</code>.
984      * <pre>
985      * &lt;build&gt;
986      *   &lt;plugins&gt;
987      *     &lt;plugin&gt;
988      *       &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
989      *       &lt;artifactId&gt;maven-javadoc-plugin&lt;/artifactId&gt;
990      *       &lt;configuration&gt;
991      *         &lt;helpfile&gt;path/to/your/resource/yourhelp-doc.html&lt;/helpfile&gt;
992      *         ...
993      *       &lt;/configuration&gt;
994      *       &lt;dependencies&gt;
995      *         &lt;dependency&gt;
996      *           &lt;groupId&gt;groupId&lt;/groupId&gt;
997      *           &lt;artifactId&gt;artifactId&lt;/artifactId&gt;
998      *           &lt;version&gt;version&lt;/version&gt;
999      *         &lt;/dependency&gt;
1000      *       &lt;/dependencies&gt;
1001      *     &lt;/plugin&gt;
1002      *     ...
1003      *   &lt;plugins&gt;
1004      * &lt;/build&gt;
1005      * </pre>
1006      * Where <code>path/to/your/resource/yourhelp-doc.html</code> is defined in the
1007      * <code>groupId:artifactId:version</code> javadoc plugin dependency.
1008      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option helpfile</a>.
1009      */
1010     @Parameter(property = "helpfile")
1011     private String helpfile;
1012 
1013     /**
1014      * Adds HTML meta keyword tags to the generated file for each class.
1015      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option keywords</a>.
1016      *
1017      * @since 2.1
1018      */
1019     @Parameter(property = "keywords", defaultValue = "false")
1020     private boolean keywords;
1021 
1022     /**
1023      * Creates links to existing javadoc-generated documentation of external referenced classes.<p>
1024      *
1025      * <b>Notes</b>:
1026      * <ol>
1027      * <li>This option is ignored if the plugin is run in offline mode using the {@code <offline>}
1028      * setting or by specifying {@code -o, --offline} or {@code -Dmaven.javadoc.offline=true} on the
1029      * command line.</li>
1030      * <li>All given links should have a fetchable <code>/package-list</code> or <code>/element-list</code>
1031      * (since Java 10). For instance:
1032      * <pre>
1033      * &lt;links&gt;
1034      *   &lt;link&gt;https://docs.oracle.com/en/java/javase/17/docs/api&lt;/link&gt;
1035      * &lt;/links&gt;
1036      * </pre>
1037      * will be used because <code>https://docs.oracle.com/en/java/javase/17/docs/api/element-list</code> exists.</li>
1038      * <li>If {@link #detectLinks} is defined, the links between the project dependencies are
1039      * automatically added.</li>
1040      * <li>If {@link #detectJavaApiLink} is defined, a Java API link, based on the Java version of the
1041      * project's sources, will be added automatically.</li>
1042      * </ol>
1043      * @see <a href=https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options>Doclet option link</a>
1044      */
1045     @Parameter(property = "links")
1046     protected ArrayList<String> links;
1047 
1048     /**
1049      * Redefine the apidoc URL for specific dependencies when using {@link #detectLinks}.
1050      * Useful if the dependency wasn't build with Maven or when the apidocs have been moved.
1051      * <pre>
1052      * &lt;dependencyLinks&gt;
1053      *   &lt;dependencyLink&gt;
1054      *     &lt;groupId&gt;groupId&lt;/groupId&gt;
1055      *     &lt;artifactId&gt;artifactId&lt;/artifactId&gt;
1056      *     &lt;classifier&gt;classifier&lt;/classifier&gt; &lt;!-- optional --&gt;
1057      *     &lt;url&gt;version&lt;/url&gt;
1058      *   &lt;/dependencyLink&gt;
1059      * &lt;/dependencyLinks&gt;
1060      * </pre>
1061      *
1062      * @see #detectLinks
1063      * @since 3.3.0
1064      */
1065     @Parameter
1066     private List<DependencyLink> dependencyLinks = new ArrayList<>();
1067 
1068     /**
1069      * Creates an HTML version of each source file (with line numbers) and adds links to them from the standard
1070      * HTML documentation.
1071      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option linksource/a>.
1072      * <br/>
1073      */
1074     @Parameter(property = "linksource", defaultValue = "false")
1075     private boolean linksource;
1076 
1077     /**
1078      * Suppress the entire comment body, including the main description and all tags, generating only declarations.
1079      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option nocomment</a>.
1080      */
1081     @Parameter(property = "nocomment", defaultValue = "false")
1082     private boolean nocomment;
1083 
1084     /**
1085      * Prevents the generation of any deprecated API at all in the documentation.
1086      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option nodeprecated</a>.
1087      * <br/>
1088      */
1089     @Parameter(property = "nodeprecated", defaultValue = "false")
1090     private boolean nodeprecated;
1091 
1092     /**
1093      * Prevents the generation of the file containing the list of deprecated APIs (deprecated-list.html) and the
1094      * link in the navigation bar to that page.
1095      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">
1096      * Doclet option nodeprecatedlist</a>.
1097      */
1098     @Parameter(property = "nodeprecatedlist", defaultValue = "false")
1099     private boolean nodeprecatedlist;
1100 
1101     /**
1102      * Omits the HELP link in the navigation bars at the top and bottom of each page of output.
1103      * <br/>
1104      * <b>Note</b>: could be in conflict with {@link #helpfile}.
1105      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option nohelp</a>.
1106      */
1107     @Parameter(property = "nohelp", defaultValue = "false")
1108     private boolean nohelp;
1109 
1110     /**
1111      * Omits the index from the generated docs.
1112      * <br/>
1113      * <b>Note</b>: could be in conflict with {@link #splitindex}
1114      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option noindex</a>.
1115      */
1116     @Parameter(property = "noindex", defaultValue = "false")
1117     private boolean noindex;
1118 
1119     /**
1120      * Omits the navigation bar from the generated docs.
1121      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option nonavbar</a>.
1122      */
1123     @Parameter(property = "nonavbar", defaultValue = "false")
1124     private boolean nonavbar;
1125 
1126     /**
1127      * Omits the entire overview page from the generated docs.
1128      * <br/>
1129      * <b>Note</b>: could be in conflict with {@link #overview}.
1130      * <br/>
1131      * Standard Doclet undocumented option.
1132      * <br/>
1133      *
1134      * @since 2.4
1135      */
1136     @Parameter(property = "nooverview", defaultValue = "false")
1137     private boolean nooverview;
1138 
1139     /**
1140      * Omits qualifying package name from ahead of class names in output.
1141      * Example:
1142      * <pre>
1143      * &lt;noqualifier&gt;all&lt;/noqualifier&gt;
1144      * or
1145      * &lt;noqualifier&gt;packagename1:packagename2&lt;/noqualifier&gt;
1146      * </pre>
1147      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option noqualifier</a>.
1148      */
1149     @Parameter(property = "noqualifier")
1150     private String noqualifier;
1151 
1152     /**
1153      * Omits the "Since" sections from the generated docs.
1154      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option nosince</a>.
1155      */
1156     @Parameter(property = "nosince", defaultValue = "false")
1157     private boolean nosince;
1158 
1159     /**
1160      * Suppresses the timestamp, which is hidden in an HTML comment in the generated HTML near the top of each page.
1161      * <br><br>
1162      * <strong>Note:</strong> If the project has the property <code>project.build.outputTimestamp</code>, the value
1163      * will be overwritten to true. This way it is possible to generate reproducible javadoc jars.
1164      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option notimestamp</a>.
1165      *
1166      * @since 2.1
1167      */
1168     @Parameter(property = "notimestamp", defaultValue = "false")
1169     private boolean notimestamp;
1170 
1171     /**
1172      * Omits the class/interface hierarchy pages from the generated docs.
1173      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option notree</a>
1174      */
1175     @Parameter(property = "notree", defaultValue = "false")
1176     private boolean notree;
1177 
1178     /**
1179      * This option is a variation of {@link #links}; they both create links to javadoc-generated documentation
1180      * for external referenced classes.
1181      * <br/>
1182      * Example:
1183      * <pre>
1184      * &lt;offlineLinks&gt;
1185      *   &lt;offlineLink&gt;
1186      *     &lt;url&gt;https://docs.oracle.com/javase/1.5.0/docs/api/&lt;/url&gt;
1187      *     &lt;location&gt;../javadoc/jdk-5.0/&lt;/location&gt;
1188      *   &lt;/offlineLink&gt;
1189      * &lt;/offlineLinks&gt;
1190      * </pre>
1191      * <br/>
1192      * <b>Note</b>: if {@link #detectOfflineLinks} is defined, the offline links between the project modules are
1193      * automatically added if the goal is calling in a non-aggregator way.
1194      * @see OfflineLink
1195      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#additional-options-provided-by-the-standard-doclet">Doclet option linkoffline</a>
1196      */
1197     @Parameter(property = "offlineLinks")
1198     private OfflineLink[] offlineLinks;
1199 
1200     /**
1201      * The shared output directory for the report where Javadoc saves the generated HTML files.
1202      * Note that this parameter is only evaluated if the goal is run directly from the command line.
1203      * If the goal is run indirectly as part of a site generation, the shared output directory configured in the
1204      * <a href="https://maven.apache.org/plugins/maven-site-plugin/site-mojo.html#outputDirectory">Maven Site Plugin</a>
1205      * is used instead.
1206      * @see org.apache.maven.reporting.AbstractMavenReport#outputDirectory
1207      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#additional-options-provided-by-the-standard-doclet">Doclet option d</a>
1208      */
1209     @Parameter(defaultValue = "${project.build.directory}/reports", required = true)
1210     protected File outputDirectory;
1211 
1212     /**
1213      * Specify the text for upper left frame.
1214      * @see <a href="https://bugs.openjdk.org/browse/JDK-4770521">Bug Report about missing documentation</a>
1215      * @since 2.1
1216      */
1217     @Parameter(property = "packagesheader")
1218     private String packagesheader;
1219 
1220     /**
1221      * Generates compile-time warnings for missing serial tags.
1222      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option serialwarn</a>
1223      */
1224     @Parameter(property = "serialwarn", defaultValue = "false")
1225     private boolean serialwarn;
1226 
1227     /**
1228      * Specify the number of spaces each tab takes up in the source. If no tab is used in source, the default
1229      * space is used.
1230      *
1231      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option sourcetab</a>
1232      * @since 2.1
1233      */
1234     @Parameter(property = "sourcetab", alias = "linksourcetab")
1235     private int sourcetab;
1236 
1237     /**
1238      * Splits the index file into multiple files, alphabetically, one file per letter, plus a file for any index
1239      * entries that start with non-alphabetical characters.
1240      * <br/>
1241      * <b>Note</b>: could be in conflict with {@link #noindex}.
1242      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option splitindex</a>.
1243      */
1244     @Parameter(property = "splitindex", defaultValue = "false")
1245     private boolean splitindex;
1246 
1247     /**
1248      * Specifies whether the stylesheet to be used is the <code>maven</code>'s javadoc stylesheet or
1249      * <code>java</code>'s default stylesheet when a {@link #stylesheetfile} parameter is not specified.
1250      * <br/>
1251      * Possible values: <code>maven<code> or <code>java</code>.
1252      * @deprecated This is no longer evaluated, instead use {@link #addStylesheets} to customize the CSS.
1253      */
1254     @Parameter(property = "stylesheet", defaultValue = "java")
1255     @Deprecated
1256     private String stylesheet;
1257 
1258     /**
1259      * Specifies the path of an alternate HTML stylesheet file.
1260      * <br/>
1261      * The <code>stylesheetfile</code> could be an absolute File path.
1262      * <br/>
1263      * Since 2.6, it could be also be a path from a resource in the current project source directories
1264      * (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
1265      * or from a resource in the Javadoc plugin dependencies, for instance:
1266      * <pre>
1267      * &lt;stylesheetfile&gt;path/to/your/resource/yourstylesheet.css&lt;/stylesheetfile&gt;
1268      * </pre>
1269      * Where <code>path/to/your/resource/yourstylesheet.css</code> could be in <code>src/main/javadoc</code>.
1270      * <pre>
1271      * &lt;build&gt;
1272      *   &lt;plugins&gt;
1273      *     &lt;plugin&gt;
1274      *       &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
1275      *       &lt;artifactId&gt;maven-javadoc-plugin&lt;/artifactId&gt;
1276      *       &lt;configuration&gt;
1277      *         &lt;stylesheetfile&gt;path/to/your/resource/yourstylesheet.css&lt;/stylesheetfile&gt;
1278      *         ...
1279      *       &lt;/configuration&gt;
1280      *       &lt;dependencies&gt;
1281      *         &lt;dependency&gt;
1282      *           &lt;groupId&gt;groupId&lt;/groupId&gt;
1283      *           &lt;artifactId&gt;artifactId&lt;/artifactId&gt;
1284      *           &lt;version&gt;version&lt;/version&gt;
1285      *         &lt;/dependency&gt;
1286      *       &lt;/dependencies&gt;
1287      *     &lt;/plugin&gt;
1288      *     ...
1289      *   &lt;plugins&gt;
1290      * &lt;/build&gt;
1291      * </pre>
1292      * Where <code>path/to/your/resource/yourstylesheet.css</code> is defined in the
1293      * <code>groupId:artifactId:version</code> javadoc plugin dependency.
1294      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option
1295      * stylesheetfile</a>.
1296      */
1297     @Parameter(property = "stylesheetfile")
1298     private String stylesheetfile;
1299 
1300     /**
1301      * Specifies the path of an additional HTML stylesheet file relative to the {@code javadocDirectory}
1302      * Example:
1303      * <pre>
1304      *     &lt;addStylesheets&gt;
1305      *         &lt;addStylesheet&gt;resources/addstylesheet.css&lt;/addStylesheet&gt;
1306      *     &lt;/addStylesheets&gt;
1307      * </pre>
1308      * @since 3.3.0
1309      */
1310     @Parameter
1311     private String[] addStylesheets;
1312 
1313     /**
1314      * Specifies the class file that starts the taglet used in generating the documentation for that tag.
1315      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option taglet</a>.
1316      */
1317     @Parameter(property = "taglet")
1318     private String taglet;
1319 
1320     /**
1321      * Specifies the Taglet artifact containing the taglet class files (.class).
1322      * <br/>
1323      * Example:
1324      * <pre>
1325      * &lt;taglets&gt;
1326      *   &lt;taglet&gt;
1327      *     &lt;tagletClass&gt;com.sun.tools.doclets.ToDoTaglet&lt;/tagletClass&gt;
1328      *   &lt;/taglet&gt;
1329      *   &lt;taglet&gt;
1330      *     &lt;tagletClass&gt;package.to.AnotherTagletClass&lt;/tagletClass&gt;
1331      *   &lt;/taglet&gt;
1332      *   ...
1333      * &lt;/taglets&gt;
1334      * &lt;tagletArtifact&gt;
1335      *   &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1336      *   &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1337      *   &lt;version&gt;version-Taglet&lt;/version&gt;
1338      * &lt;/tagletArtifact&gt;
1339      * </pre>
1340      * <br/>
1341      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/TagletArtifact.html">Javadoc</a>.
1342      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option tagletpath</a>.
1343      * @since 2.1
1344      */
1345     @Parameter(property = "tagletArtifact")
1346     private TagletArtifact tagletArtifact;
1347 
1348     /**
1349      * Specifies several Taglet artifacts containing the taglet class files (.class). These taglet class names will be
1350      * auto-detected and so no need to specify them.
1351      * <br/>
1352      * Example:
1353      * <pre>
1354      * &lt;tagletArtifacts&gt;
1355      *   &lt;tagletArtifact&gt;
1356      *     &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1357      *     &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1358      *     &lt;version&gt;version-Taglet&lt;/version&gt;
1359      *   &lt;/tagletArtifact&gt;
1360      *   ...
1361      * &lt;/tagletArtifacts&gt;
1362      * </pre>
1363      * <br/>
1364      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/TagletArtifact.html">Javadoc</a>.
1365      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet options taglet and tagletpath</a>
1366      * @since 2.5
1367      */
1368     @Parameter(property = "tagletArtifacts")
1369     private TagletArtifact[] tagletArtifacts;
1370 
1371     /**
1372      * Specifies the search paths for finding taglet class files (.class). The <code>tagletpath</code> can contain
1373      * multiple paths by separating them with a colon (<code>:</code>) or a semicolon (<code>;</code>).
1374      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option tagletpath</a>.
1375      */
1376     @Parameter(property = "tagletpath")
1377     private String tagletpath;
1378 
1379     /**
1380      * Enables the Javadoc tool to interpret multiple taglets.
1381      * <br/>
1382      * Example:
1383      * <pre>
1384      * &lt;taglets&gt;
1385      *   &lt;taglet&gt;
1386      *     &lt;tagletClass&gt;com.sun.tools.doclets.ToDoTaglet&lt;/tagletClass&gt;
1387      *     &lt;!--&lt;tagletpath&gt;/home/taglets&lt;/tagletpath&gt;--&gt;
1388      *     &lt;tagletArtifact&gt;
1389      *       &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1390      *       &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1391      *       &lt;version&gt;version-Taglet&lt;/version&gt;
1392      *     &lt;/tagletArtifact&gt;
1393      *   &lt;/taglet&gt;
1394      * &lt;/taglets&gt;
1395      * </pre>
1396      * <br/>
1397      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/Taglet.html">Javadoc</a>.
1398      * <br/>
1399      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet options taglet and tagletpath</a>
1400      * @since 2.1
1401      */
1402     @Parameter(property = "taglets")
1403     private Taglet[] taglets;
1404 
1405     /**
1406      * Enables the Javadoc tool to interpret a simple, one-argument custom block tag tagname in doc comments.
1407      * <br/>
1408      * Example:
1409      * <pre>
1410      * &lt;tags&gt;
1411      *   &lt;tag&gt;
1412      *     &lt;name&gt;todo&lt;/name&gt;
1413      *     &lt;placement&gt;a&lt;/placement&gt;
1414      *     &lt;head&gt;To Do:&lt;/head&gt;
1415      *   &lt;/tag&gt;
1416      * &lt;/tags&gt;
1417      * </pre>
1418      * <b>Note</b>: the placement should be a combinaison of Xaoptcmf letters:
1419      * <ul>
1420      * <li><b><code>X</code></b> (disable tag)</li>
1421      * <li><b><code>a</code></b> (all)</li>
1422      * <li><b><code>o</code></b> (overview)</li>
1423      * <li><b><code>p</code></b> (packages)</li>
1424      * <li><b><code>t</code></b> (types, that is classes and interfaces)</li>
1425      * <li><b><code>c</code></b> (constructors)</li>
1426      * <li><b><code>m</code></b> (methods)</li>
1427      * <li><b><code>f</code></b> (fields)</li>
1428      * </ul>
1429      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/Tag.html">Javadoc</a>.
1430      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option tag</a>.
1431      */
1432     @Parameter(property = "tags")
1433     private Tag[] tags;
1434 
1435     /**
1436      * Specifies the top text to be placed at the top of each output file.
1437      * @see <a href="https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6227616">Java Bug 6227616</a>.
1438      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option top</a>.
1439      * @since 2.4
1440      */
1441     @Parameter(property = "top")
1442     private String top;
1443 
1444     /**
1445      * Includes one "Use" page for each documented class and package.
1446      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option use</a>.
1447      */
1448     @Parameter(property = "use", defaultValue = "true")
1449     private boolean use;
1450 
1451     /**
1452      * Includes the given version text in the generated docs.
1453      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option version</a>.
1454      */
1455     @Parameter(property = "version", defaultValue = "true")
1456     private boolean version;
1457 
1458     /**
1459      * Specifies the title to be placed in the HTML title tag.
1460      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">Doclet option windowtitle</a>.
1461      */
1462     @Parameter(property = "windowtitle", defaultValue = "${project.name} ${project.version} API")
1463     private String windowtitle;
1464 
1465     /**
1466      * Whether dependency -sources jars should be resolved and included as source paths for javadoc generation.
1467      * This is useful when creating javadocs for a distribution project.
1468      *
1469      * @since 2.7
1470      */
1471     @Parameter(defaultValue = "false")
1472     private boolean includeDependencySources;
1473 
1474     /**
1475      * Directory where unpacked project sources / test-sources should be cached.
1476      *
1477      * @see #includeDependencySources
1478      * @since 2.7
1479      */
1480     @Parameter(defaultValue = "${project.build.directory}/distro-javadoc-sources")
1481     private File sourceDependencyCacheDir;
1482 
1483     /**
1484      * Whether to include transitive dependencies in the list of dependency -sources jars to include in this javadoc
1485      * run.
1486      *
1487      * @see #includeDependencySources
1488      * @since 2.7
1489      * @deprecated if these sources depend on transitive dependencies, those dependencies should be added to the pom as
1490      *             direct dependencies
1491      */
1492     @Parameter(defaultValue = "false")
1493     @Deprecated
1494     private boolean includeTransitiveDependencySources;
1495 
1496     /**
1497      * List of included dependency-source patterns. Example: <code>org.apache.maven:*</code>
1498      *
1499      * @see #includeDependencySources
1500      * @since 2.7
1501      */
1502     @Parameter
1503     private List<String> dependencySourceIncludes;
1504 
1505     /**
1506      * List of excluded dependency-source patterns. Example: <code>org.apache.maven.shared:*</code>
1507      *
1508      * @see #includeDependencySources
1509      * @since 2.7
1510      */
1511     @Parameter
1512     private List<String> dependencySourceExcludes;
1513 
1514     /**
1515      * Directory into which assembled {@link JavadocOptions} instances will be written before they
1516      * are added to javadoc resources bundles.
1517      *
1518      * @since 2.7
1519      */
1520     @Parameter(defaultValue = "${project.build.directory}/javadoc-bundle-options", readonly = true)
1521     private File javadocOptionsDir;
1522 
1523     /**
1524      * Transient variable to allow lazy-resolution of javadoc bundles from dependencies, so they can
1525      * be used at various points in the javadoc generation process.
1526      *
1527      * @since 2.7
1528      */
1529     private transient List<JavadocBundle> dependencyJavadocBundles;
1530 
1531     /**
1532      * Capability to add additional dependencies to the javadoc classpath.
1533      * Example:
1534      * <pre>
1535      * &lt;additionalDependencies&gt;
1536      *   &lt;additionalDependency&gt;
1537      *     &lt;groupId&gt;geronimo-spec&lt;/groupId&gt;
1538      *     &lt;artifactId&gt;geronimo-spec-jta&lt;/artifactId&gt;
1539      *     &lt;version&gt;1.0.1B-rc4&lt;/version&gt;
1540      *   &lt;/additionalDependency&gt;
1541      * &lt;/additionalDependencies&gt;
1542      * </pre>
1543      *
1544      * @since 2.8.1
1545      */
1546     @Parameter
1547     private List<AdditionalDependency> additionalDependencies;
1548 
1549     /**
1550      * Include filters on the source files. Default is **\/\*.java.
1551      * These are ignored if you specify subpackages or subpackage excludes.
1552      *
1553      * @since 2.9
1554      */
1555     @Parameter
1556     private List<String> sourceFileIncludes;
1557 
1558     /**
1559      * exclude filters on the source files.
1560      * These are ignored if you specify subpackages or subpackage excludes.
1561      *
1562      * @since 2.9
1563      */
1564     @Parameter
1565     private List<String> sourceFileExcludes;
1566 
1567     /**
1568      * No-op.
1569      *
1570      * @deprecated the security fix this applied is not needed in Java 8+ or the most recent
1571      *     versions of JDK 6 and 7.
1572      * @since 2.9.1
1573      */
1574     @Deprecated
1575     @Parameter(defaultValue = "true", property = "maven.javadoc.applyJavadocSecurityFix")
1576     private boolean applyJavadocSecurityFix = true;
1577 
1578     /**
1579      * <p>
1580      * Allow for configuration of the javadoc tool via maven toolchains.
1581      * This overrules the toolchain selected by the maven-toolchain-plugin.
1582      * </p>
1583      *
1584      * <p>Examples:</p>
1585      * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html">
1586      *     Guide to Toolchains</a> for more info)
1587      *
1588      * <pre>
1589      * {@code
1590      *    <configuration>
1591      *        ...
1592      *        <jdkToolchain>
1593      *            <version>11</version>
1594      *        </jdkToolchain>
1595      *    </configuration>
1596      *
1597      *    <configuration>
1598      *        ...
1599      *        <jdkToolchain>
1600      *            <version>1.8</version>
1601      *            <vendor>zulu</vendor>
1602      *        </jdkToolchain>
1603      *    </configuration>
1604      *    }
1605      * </pre>
1606      *
1607      * <strong>note:</strong> requires at least Maven 3.3.1
1608      *
1609      * @since 3.0.0
1610      */
1611     @Parameter
1612     private Map<String, String> jdkToolchain;
1613 
1614     /**
1615      * <p>
1616      * Location of the file used to store the state of the previous javadoc run.
1617      * This is used to skip the generation if nothing has changed.
1618      * </p>
1619      *
1620      * @since 3.2.0
1621      */
1622     @Parameter(
1623             property = "staleDataPath",
1624             defaultValue = "${project.build.directory}/maven-javadoc-plugin-stale-data.txt")
1625     private File staleDataPath;
1626 
1627     /**
1628      * <p>
1629      * Comma separated list of modules (can be regular expression) in the format ([group:]artifactId) to not add in aggregated javadoc
1630      * </p>
1631      *
1632      * @since 3.2.0
1633      */
1634     @Parameter(property = "maven.javadoc.skippedModules")
1635     private String skippedModules;
1636 
1637     /**
1638      * List built once from the parameter {@link #skippedModules}
1639      */
1640     private List<Pattern> patternsToSkip;
1641 
1642     /**
1643      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
1644      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
1645      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
1646      *
1647      * @since 3.2.0
1648      */
1649     @Parameter(defaultValue = "${project.build.outputTimestamp}")
1650     protected String outputTimestamp;
1651 
1652     /**
1653      * Forces the Javadoc JVM locale to be {@link Locale#ROOT}. This will force the Javadoc output
1654      * on {@code stdout} and {@code stderr} to be in English only and the generated HTML content in
1655      * English as well. If you need the generated HTML content in another supported language use
1656      * {@link #locale}.
1657      *
1658      * @since 3.8.0
1659      */
1660     @Parameter(property = "forceRootLocale", defaultValue = "true")
1661     private boolean forceRootLocale;
1662 
1663     /**
1664      * From jdk 23, javadoc now include some extra css, files which can increase the size of javadoc jar.
1665      * This can be disabled using the option {@code --no-fonts}
1666      * The javadoc plugin will add this option to disable extra files to be added per default.
1667      * If you prefer the new font you must set to {@code true} the option {@code disableNoFonts}
1668      * @since 3.11.3
1669      */
1670     @Parameter(property = "maven.javadoc.disableNoFonts", defaultValue = "false")
1671     private boolean disableNoFonts;
1672 
1673     // ----------------------------------------------------------------------
1674     // protected methods
1675     // ----------------------------------------------------------------------
1676 
1677     /**
1678      * Indicates whether this goal is flagged with <code>@aggregator</code>.
1679      *
1680      * @return <code>true</code> if the goal is designed as an aggregator, <code>false</code> otherwise.
1681      * @see AggregatorJavadocReport
1682      * @see AggregatorTestJavadocReport
1683      */
1684     protected boolean isAggregator() {
1685         return false;
1686     }
1687 
1688     /**
1689      * Indicates whether this goal generates documentation for the <code>Java Test code</code>.
1690      *
1691      * @return <code>true</code> if the goal generates Test Javadocs, <code>false</code> otherwise.
1692      */
1693     protected boolean isTest() {
1694         return false;
1695     }
1696 
1697     protected String getOutputDirectory() {
1698         return outputDirectory.getAbsolutePath();
1699     }
1700 
1701     /**
1702      * Method that returns the plugin report output directory where the generated Javadoc report will be put
1703      * beneath {@link #getOutputDirectory()}/{@link org.apache.maven.reporting.AbstractMavenReport#getReportOutputDirectory()}.
1704      *
1705      * @return a String that contains the target directory
1706      */
1707     protected String getPluginReportOutputDirectory() {
1708         return getOutputDirectory() + "/" + (isTest() ? "test" : "") + "apidocs";
1709     }
1710 
1711     protected MavenProject getProject() {
1712         return project;
1713     }
1714 
1715     /**
1716      * @param p not null maven project
1717      * @return the list of directories where compiled classes are placed for the given project. These dirs are
1718      *         added to the javadoc classpath.
1719      */
1720     protected List<File> getProjectBuildOutputDirs(MavenProject p) {
1721         if (StringUtils.isEmpty(p.getBuild().getOutputDirectory())) {
1722             return Collections.emptyList();
1723         }
1724 
1725         return Collections.singletonList(new File(p.getBuild().getOutputDirectory()));
1726     }
1727 
1728     /**
1729      * @param project the project in which to find a classes file
1730      * @return null, the attached artifact file, or outputDirectory.
1731      */
1732     protected File getClassesFile(MavenProject project) {
1733         if (!isAggregator() && isTest()) {
1734             return null;
1735         }
1736 
1737         if (project.getArtifact() != null && project.getArtifact().getFile() != null) {
1738             File artifactFile = project.getArtifact().getFile();
1739             if (artifactFile.isDirectory() || artifactFile.getName().endsWith(".jar")) {
1740                 return artifactFile;
1741             }
1742         } else if (project.getExecutionProject() != null
1743                 && project.getExecutionProject().getArtifact() != null
1744                 && project.getExecutionProject().getArtifact().getFile() != null) {
1745             File artifactFile = project.getExecutionProject().getArtifact().getFile();
1746             if (artifactFile.isDirectory() || artifactFile.getName().endsWith(".jar")) {
1747                 return artifactFile;
1748             }
1749         }
1750 
1751         if (project.getBuild().getOutputDirectory() != null) {
1752             return new File(project.getBuild().getOutputDirectory());
1753         } else {
1754             return null;
1755         }
1756     }
1757 
1758     /**
1759      * @param p not null maven project
1760      * @return the list of source paths for the given project
1761      */
1762     protected List<String> getProjectSourceRoots(MavenProject p) {
1763         if ("pom".equals(p.getPackaging().toLowerCase(Locale.ENGLISH))) {
1764             return Collections.emptyList();
1765         }
1766 
1767         return p.getCompileSourceRoots() == null
1768                 ? Collections.emptyList()
1769                 : new LinkedList<>(p.getCompileSourceRoots());
1770     }
1771 
1772     /**
1773      * @param p not null maven project
1774      * @return the list of source paths for the execution project of the given project
1775      */
1776     protected List<String> getExecutionProjectSourceRoots(MavenProject p) {
1777         if ("pom".equals(p.getExecutionProject().getPackaging().toLowerCase(Locale.ENGLISH))) {
1778             return Collections.emptyList();
1779         }
1780 
1781         return p.getExecutionProject().getCompileSourceRoots() == null
1782                 ? Collections.emptyList()
1783                 : new LinkedList<>(p.getExecutionProject().getCompileSourceRoots());
1784     }
1785 
1786     /**
1787      * @return the current javadoc directory
1788      */
1789     protected File getJavadocDirectory() {
1790         return javadocDirectory;
1791     }
1792 
1793     /**
1794      * @return the doclint specific checks configuration
1795      */
1796     protected String getDoclint() {
1797         return doclint;
1798     }
1799 
1800     /**
1801      * @return the title to be placed near the top of the overview summary file
1802      */
1803     protected String getDoctitle() {
1804         return doctitle;
1805     }
1806 
1807     /**
1808      * @return the overview documentation file from the user parameter or from the <code>javadocdirectory</code>
1809      */
1810     protected File getOverview() {
1811         return overview;
1812     }
1813 
1814     /**
1815      * @return the title to be placed in the HTML title tag
1816      */
1817     protected String getWindowtitle() {
1818         return windowtitle;
1819     }
1820 
1821     /**
1822      * @return the charset attribute or the value of {@link #getDocencoding()} if <code>null</code>.
1823      */
1824     private String getCharset() {
1825         return (charset == null || charset.isEmpty()) ? getDocencoding() : charset;
1826     }
1827 
1828     /**
1829      * @return the docencoding attribute or <code>UTF-8</code> if <code>null</code>.
1830      */
1831     private String getDocencoding() {
1832         return (docencoding == null || docencoding.isEmpty()) ? StandardCharsets.UTF_8.name() : docencoding;
1833     }
1834 
1835     /**
1836      * @return the encoding attribute or the value of <code>file.encoding</code> system property if <code>null</code>.
1837      */
1838     private String getEncoding() {
1839         return (encoding == null || encoding.isEmpty())
1840                 ? Charset.defaultCharset().name()
1841                 : encoding;
1842     }
1843 
1844     @Override
1845     public void execute() throws MojoExecutionException, MojoFailureException {
1846         verifyRemovedParameter("aggregator");
1847         verifyRemovedParameter("proxyHost");
1848         verifyRemovedParameter("proxyPort");
1849         verifyReplacedParameter("additionalparam", "additionalOptions");
1850 
1851         doExecute();
1852     }
1853 
1854     protected abstract void doExecute() throws MojoExecutionException, MojoFailureException;
1855 
1856     protected final void verifyRemovedParameter(String paramName) {
1857         Xpp3Dom configDom = mojoExecution.getConfiguration();
1858         if (configDom != null) {
1859             if (configDom.getChild(paramName) != null) {
1860                 throw new IllegalArgumentException(
1861                         "parameter '" + paramName + "' has been removed from the plugin, please verify documentation.");
1862             }
1863         }
1864     }
1865 
1866     private void verifyReplacedParameter(String oldParamName, String newParamNew) {
1867         Xpp3Dom configDom = mojoExecution.getConfiguration();
1868         if (configDom != null) {
1869             if (configDom.getChild(oldParamName) != null) {
1870                 throw new IllegalArgumentException("parameter '" + oldParamName + "' has been replaced with "
1871                         + newParamNew + ", please verify documentation.");
1872             }
1873         }
1874     }
1875 
1876     /**
1877      * The <a href="package-summary.html">package documentation</a> details the
1878      * Javadoc Options used by this Plugin.
1879      *
1880      * @param unusedLocale the wanted locale (actually unused).
1881      * @throws MavenReportException if any
1882      */
1883     protected void executeReport(Locale unusedLocale) throws MavenReportException {
1884         if (getLog().isDebugEnabled()) {
1885             this.debug = true;
1886         }
1887 
1888         // NOTE: Always generate this file, to allow javadocs from modules to be aggregated via
1889         // useDependencySources in a distro module build.
1890         try {
1891             buildJavadocOptions();
1892         } catch (IOException e) {
1893             throw new MavenReportException("Failed to generate javadoc options file: " + e.getMessage(), e);
1894         }
1895 
1896         Collection<JavadocModule> sourcePaths = getSourcePaths();
1897 
1898         Collection<Path> collectedSourcePaths =
1899                 sourcePaths.stream().flatMap(e -> e.getSourcePaths().stream()).collect(Collectors.toList());
1900 
1901         Map<Path, Collection<String>> files = getFiles(collectedSourcePaths);
1902         if (!canGenerateReport(files)) {
1903             return;
1904         }
1905 
1906         // ----------------------------------------------------------------------
1907         // Find the javadoc executable and version
1908         // ----------------------------------------------------------------------
1909 
1910         String jExecutable;
1911         try {
1912             jExecutable = getJavadocExecutable();
1913         } catch (IOException e) {
1914             throw new MavenReportException("Unable to find javadoc command: " + e.getMessage(), e);
1915         }
1916         setFJavadocVersion(new File(jExecutable));
1917 
1918         Collection<String> packageNames;
1919         if (javadocRuntimeVersion.isAtLeast("9")) {
1920             packageNames = getPackageNamesRespectingJavaModules(sourcePaths);
1921         } else {
1922             packageNames = getPackageNames(files);
1923         }
1924 
1925         // ----------------------------------------------------------------------
1926         // Javadoc output directory as File
1927         // ----------------------------------------------------------------------
1928 
1929         File javadocOutputDirectory = new File(getPluginReportOutputDirectory());
1930         if (javadocOutputDirectory.exists() && !javadocOutputDirectory.isDirectory()) {
1931             throw new MavenReportException("IOException: " + javadocOutputDirectory + " is not a directory.");
1932         }
1933         if (javadocOutputDirectory.exists() && !javadocOutputDirectory.canWrite()) {
1934             throw new MavenReportException("IOException: " + javadocOutputDirectory + " is not writable.");
1935         }
1936         javadocOutputDirectory.mkdirs();
1937 
1938         // ----------------------------------------------------------------------
1939         // Copy all resources
1940         // ----------------------------------------------------------------------
1941 
1942         copyAllResources(javadocOutputDirectory);
1943 
1944         // ----------------------------------------------------------------------
1945         // Create command line for Javadoc
1946         // ----------------------------------------------------------------------
1947 
1948         Commandline cmd = new Commandline();
1949         cmd.getShell().setQuotedArgumentsEnabled(false); // for Javadoc JVM args
1950         cmd.setWorkingDirectory(javadocOutputDirectory.getAbsolutePath());
1951         cmd.setExecutable(jExecutable);
1952 
1953         // ----------------------------------------------------------------------
1954         // Wrap Javadoc JVM args
1955         // ----------------------------------------------------------------------
1956 
1957         addMemoryArg(cmd, "-Xmx", this.maxmemory);
1958         addMemoryArg(cmd, "-Xms", this.minmemory);
1959         addProxyArg(cmd);
1960 
1961         if (forceRootLocale) {
1962             cmd.createArg().setValue("-J-Duser.language=");
1963             cmd.createArg().setValue("-J-Duser.country=");
1964         }
1965 
1966         if (additionalJOption != null && !additionalJOption.isEmpty()) {
1967             cmd.createArg().setValue(additionalJOption);
1968         }
1969 
1970         if (additionalJOptions != null && additionalJOptions.length != 0) {
1971             for (String jo : additionalJOptions) {
1972                 cmd.createArg().setValue(jo);
1973             }
1974         }
1975 
1976         // ----------------------------------------------------------------------
1977         // Wrap Standard doclet Options
1978         // ----------------------------------------------------------------------
1979         List<String> standardDocletArguments = new ArrayList<>();
1980 
1981         Set<OfflineLink> offlineLinks;
1982         if ((doclet == null || doclet.isEmpty()) || useStandardDocletOptions) {
1983             offlineLinks = getOfflineLinks();
1984             addStandardDocletOptions(javadocOutputDirectory, standardDocletArguments, offlineLinks);
1985         } else {
1986             offlineLinks = Collections.emptySet();
1987         }
1988 
1989         // ----------------------------------------------------------------------
1990         // Wrap Javadoc options
1991         // ----------------------------------------------------------------------
1992         List<String> javadocArguments = new ArrayList<>();
1993 
1994         if (JavaVersion.JAVA_VERSION.isAtLeast("23") && !disableNoFonts) {
1995             javadocArguments.add("--no-fonts");
1996         }
1997 
1998         addJavadocOptions(javadocOutputDirectory, javadocArguments, sourcePaths, offlineLinks);
1999 
2000         // ----------------------------------------------------------------------
2001         // Write options file and include it in the command line
2002         // ----------------------------------------------------------------------
2003 
2004         List<String> arguments = new ArrayList<>(javadocArguments.size() + standardDocletArguments.size());
2005         arguments.addAll(javadocArguments);
2006         arguments.addAll(standardDocletArguments);
2007 
2008         if (arguments.size() > 0) {
2009             addCommandLineOptions(cmd, arguments, javadocOutputDirectory);
2010         }
2011 
2012         // ----------------------------------------------------------------------
2013         // Write packages file and include it in the command line
2014         // ----------------------------------------------------------------------
2015 
2016         // MJAVADOC-365 if includes/excludes are specified, these take precedence over the default
2017         // package-based mode and force javadoc into file-based mode unless subpackages are
2018         // specified. Subpackages take precedence over file-based include/excludes. Why? Because
2019         // getFiles(...) returns an empty list when subpackages are specified.
2020         boolean includesExcludesActive = (sourceFileIncludes != null && !sourceFileIncludes.isEmpty())
2021                 || (sourceFileExcludes != null && !sourceFileExcludes.isEmpty());
2022         if (includesExcludesActive && !(subpackages == null || subpackages.isEmpty())) {
2023             getLog().warn("sourceFileIncludes and sourceFileExcludes have no effect when subpackages are specified!");
2024             includesExcludesActive = false;
2025         }
2026         if (!packageNames.isEmpty() && !includesExcludesActive && !legacyMode) {
2027             addCommandLinePackages(cmd, javadocOutputDirectory, packageNames);
2028 
2029             // ----------------------------------------------------------------------
2030             // Write argfile file and include it in the command line
2031             // ----------------------------------------------------------------------
2032 
2033             List<String> specialFiles = getSpecialFiles(files);
2034 
2035             if (!specialFiles.isEmpty()) {
2036                 addCommandLineArgFile(cmd, javadocOutputDirectory, specialFiles);
2037             }
2038         } else {
2039             // ----------------------------------------------------------------------
2040             // Write argfile file and include it in the command line
2041             // ----------------------------------------------------------------------
2042 
2043             List<String> allFiles = new ArrayList<>();
2044             for (Map.Entry<Path, Collection<String>> filesEntry : files.entrySet()) {
2045                 for (String file : filesEntry.getValue()) {
2046                     allFiles.add(filesEntry.getKey().resolve(file).toString());
2047                 }
2048             }
2049 
2050             if (!files.isEmpty()) {
2051                 addCommandLineArgFile(cmd, javadocOutputDirectory, allFiles);
2052             }
2053         }
2054 
2055         // ----------------------------------------------------------------------
2056         // Execute command line
2057         // ----------------------------------------------------------------------
2058 
2059         executeJavadocCommandLine(cmd, javadocOutputDirectory);
2060 
2061         // delete generated javadoc files only if no error and no debug mode
2062         // [MJAVADOC-336] Use File.delete() instead of File.deleteOnExit() to
2063         // prevent these files from making their way into archives.
2064         if (!debug) {
2065             for (int i = 0; i < cmd.getArguments().length; i++) {
2066                 String arg = cmd.getArguments()[i].trim();
2067 
2068                 if (!arg.startsWith("@")) {
2069                     continue;
2070                 }
2071 
2072                 File argFile = new File(javadocOutputDirectory, arg.substring(1));
2073                 if (argFile.exists()) {
2074                     argFile.delete();
2075                 }
2076             }
2077 
2078             File scriptFile = new File(javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME);
2079             if (scriptFile.exists()) {
2080                 scriptFile.delete();
2081             }
2082         }
2083     }
2084 
2085     /**
2086      * Method to get the files on the specified source paths
2087      *
2088      * @param sourcePaths a Collection that contains the paths to the source files
2089      * @return a List that contains the specific path for every source file
2090      * @throws MavenReportException {@link MavenReportException} issue while generating report
2091      */
2092     protected Map<Path, Collection<String>> getFiles(Collection<Path> sourcePaths) throws MavenReportException {
2093         Map<Path, Collection<String>> mappedFiles = new LinkedHashMap<>(sourcePaths.size());
2094         if (subpackages == null || subpackages.isEmpty()) {
2095             Collection<String> excludedPackages = getExcludedPackages();
2096 
2097             // if legacy mode is active, treat it like pre-Java 9 (exclude module-info),
2098             // otherwise don't auto-exclude anything. Do this regardless of the release
2099             // or source values specified
2100             boolean autoExclude = legacyMode;
2101             if (!autoExclude) {
2102                 if (release != null) {
2103                     autoExclude = JavaVersion.parse(release).isBefore("9");
2104                 } else if (source != null) {
2105                     autoExclude = JavaVersion.parse(source).isBefore("9");
2106                 }
2107             }
2108 
2109             for (Path sourcePath : sourcePaths) {
2110                 File sourceDirectory = sourcePath.toFile();
2111                 ArrayList<String> files = new ArrayList<>(JavadocUtil.getFilesFromSource(
2112                         sourceDirectory, sourceFileIncludes, sourceFileExcludes, excludedPackages));
2113 
2114                 // in the aggregate goal (and theoretically in others too), there can be
2115                 // more than one module-info.java. Filter out all of them.
2116                 if (autoExclude && files.removeIf(s -> s.endsWith("module-info.java"))) {
2117                     getLog().debug("Auto exclude module-info.java due to source value");
2118                 }
2119                 mappedFiles.put(sourcePath, files);
2120             }
2121         }
2122 
2123         return mappedFiles;
2124     }
2125 
2126     /**
2127      * Method to get the source paths per reactorProject. If no source path is specified in the parameter, the compile
2128      * source roots of the project will be used.
2129      *
2130      * @return a Map of the project absolute source paths per projects key (G:A)
2131      * @throws MavenReportException {@link MavenReportException} issue while generating report
2132      * @see JavadocUtil#pruneDirs(MavenProject, Collection)
2133      */
2134     protected Collection<JavadocModule> getSourcePaths() throws MavenReportException {
2135         Collection<JavadocModule> mappedSourcePaths = new ArrayList<>();
2136 
2137         if (sourcepath == null || sourcepath.isEmpty()) {
2138             if (!"pom".equals(project.getPackaging())) {
2139                 Set<Path> sourcePaths =
2140                         new LinkedHashSet<>(JavadocUtil.pruneDirs(project, getProjectSourceRoots(project)));
2141 
2142                 if (project.getExecutionProject() != null) {
2143                     sourcePaths.addAll(JavadocUtil.pruneDirs(project, getExecutionProjectSourceRoots(project)));
2144                 }
2145 
2146                 /*
2147                  * Should be after the source path (i.e. -sourcepath '.../src/main/java;.../src/main/javadoc') and *not*
2148                  * the opposite. If not, the javadoc tool always copies doc files, even if -docfilessubdirs is not
2149                  * set.
2150                  */
2151                 if (getJavadocDirectory() != null) {
2152                     File javadocDir = getJavadocDirectory();
2153                     if (javadocDir.exists() && javadocDir.isDirectory()) {
2154                         Collection<Path> l = JavadocUtil.pruneDirs(
2155                                 project,
2156                                 Collections.singletonList(getJavadocDirectory().getAbsolutePath()));
2157                         sourcePaths.addAll(l);
2158                     }
2159                 }
2160                 if (!sourcePaths.isEmpty()) {
2161                     mappedSourcePaths.add(buildJavadocModule(project, sourcePaths));
2162                 }
2163             }
2164 
2165             if (isAggregator()) {
2166                 for (MavenProject subProject : getAggregatedProjects()) {
2167                     if (subProject != project) {
2168                         Collection<Path> additionalSourcePaths = new ArrayList<>();
2169 
2170                         List<String> sourceRoots = new ArrayList<>(getProjectSourceRoots(subProject));
2171 
2172                         if (subProject.getExecutionProject() != null) {
2173                             sourceRoots.addAll(getExecutionProjectSourceRoots(subProject));
2174                         }
2175 
2176                         ArtifactHandler artifactHandler =
2177                                 subProject.getArtifact().getArtifactHandler();
2178                         if ("java".equals(artifactHandler.getLanguage())) {
2179                             additionalSourcePaths.addAll(JavadocUtil.pruneDirs(subProject, sourceRoots));
2180                         }
2181 
2182                         if (getJavadocDirectory() != null) {
2183                             Path base = project.getBasedir().toPath();
2184                             Path relative = base.relativize(
2185                                     getJavadocDirectory().toPath().toAbsolutePath());
2186                             Path javadocDir = subProject.getBasedir().toPath().resolve(relative);
2187                             if (Files.isDirectory(javadocDir)) {
2188                                 Collection<Path> l = JavadocUtil.pruneDirs(
2189                                         subProject,
2190                                         Collections.singletonList(
2191                                                 javadocDir.toAbsolutePath().toString()));
2192                                 additionalSourcePaths.addAll(l);
2193                             }
2194                         }
2195 
2196                         if (!additionalSourcePaths.isEmpty()) {
2197                             mappedSourcePaths.add(buildJavadocModule(subProject, additionalSourcePaths));
2198                         }
2199                     }
2200                 }
2201             }
2202 
2203             if (includeDependencySources) {
2204                 mappedSourcePaths.addAll(getDependencySourcePaths());
2205             }
2206         } else {
2207             Collection<Path> sourcePaths =
2208                     JavadocUtil.pruneDirs(project, new ArrayList<>(Arrays.asList(JavadocUtil.splitPath(sourcepath))));
2209             if (getJavadocDirectory() != null) {
2210                 Collection<Path> l = JavadocUtil.pruneDirs(
2211                         project, Collections.singletonList(getJavadocDirectory().getAbsolutePath()));
2212                 sourcePaths.addAll(l);
2213             }
2214 
2215             if (!sourcePaths.isEmpty()) {
2216                 mappedSourcePaths.add(new JavadocModule(
2217                         ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion()),
2218                         getClassesFile(project),
2219                         sourcePaths));
2220             }
2221         }
2222 
2223         return mappedSourcePaths;
2224     }
2225 
2226     private JavadocModule buildJavadocModule(MavenProject project, Collection<Path> sourcePaths) {
2227         File classessFile = getClassesFile(project);
2228         ResolvePathResult resolvePathResult = getResolvePathResult(classessFile);
2229         if (resolvePathResult == null) {
2230             return new JavadocModule(
2231                     ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion()),
2232                     classessFile,
2233                     sourcePaths);
2234         } else {
2235             return new JavadocModule(
2236                     ArtifactUtils.key(project.getGroupId(), project.getArtifactId(), project.getVersion()),
2237                     classessFile,
2238                     sourcePaths,
2239                     resolvePathResult.getModuleDescriptor(),
2240                     resolvePathResult.getModuleNameSource());
2241         }
2242     }
2243 
2244     /**
2245      * Recursively add the modules of the aggregatedProject to the set of aggregatedModules.
2246      *
2247      * @param aggregatedProject the project being aggregated
2248      * @param reactorProjectsMap map of (still) available reactor projects
2249      */
2250     private Set<MavenProject> modulesForAggregatedProject(
2251             MavenProject aggregatedProject, Map<Path, MavenProject> reactorProjectsMap) {
2252         // Maven does not supply an easy way to get the projects representing
2253         // the modules of a project. So we will get the paths to the base
2254         // directories of the modules from the project and compare with the
2255         // base directories of the projects in the reactor.
2256 
2257         if (aggregatedProject.getModules().isEmpty()) {
2258             return Collections.singleton(aggregatedProject);
2259         }
2260 
2261         Path basePath = aggregatedProject.getBasedir().toPath();
2262         List<Path> modulePaths = new LinkedList<>();
2263         for (String module : aggregatedProject.getModules()) {
2264             modulePaths.add(basePath.resolve(module).normalize());
2265         }
2266 
2267         Set<MavenProject> aggregatedModules = new LinkedHashSet<>();
2268 
2269         for (Path modulePath : modulePaths) {
2270             MavenProject module = reactorProjectsMap.remove(modulePath);
2271             if (module != null) {
2272                 aggregatedModules.addAll(modulesForAggregatedProject(module, reactorProjectsMap));
2273             }
2274         }
2275 
2276         return aggregatedModules;
2277     }
2278 
2279     /**
2280      * Override this method to customize the configuration for resolving dependency sources. The default
2281      * behavior enables the resolution of -sources jar files.
2282      * @param config {@link SourceResolverConfig}
2283      * @return {@link SourceResolverConfig}
2284      */
2285     protected SourceResolverConfig configureDependencySourceResolution(final SourceResolverConfig config) {
2286         return config.withCompileSources();
2287     }
2288 
2289     /**
2290      * Resolve dependency sources so they can be included directly in the javadoc process. To customize this,
2291      * override {@link AbstractJavadocMojo#configureDependencySourceResolution(SourceResolverConfig)}.
2292      * @return List of source paths.
2293      * @throws MavenReportException {@link MavenReportException}
2294      */
2295     protected final Collection<JavadocModule> getDependencySourcePaths() throws MavenReportException {
2296         try {
2297             if (sourceDependencyCacheDir.exists()) {
2298                 FileUtils.forceDelete(sourceDependencyCacheDir);
2299                 sourceDependencyCacheDir.mkdirs();
2300             }
2301         } catch (IOException e) {
2302             throw new MavenReportException(
2303                     "Failed to delete cache directory: " + sourceDependencyCacheDir + "\nReason: " + e.getMessage(), e);
2304         }
2305 
2306         final SourceResolverConfig config = getDependencySourceResolverConfig();
2307 
2308         try {
2309             return resourceResolver.resolveDependencySourcePaths(config);
2310         } catch (org.apache.maven.artifact.resolver.ArtifactResolutionException
2311                 | org.apache.maven.artifact.resolver.ArtifactNotFoundException e) {
2312             throw new MavenReportException(
2313                     "Failed to resolve one or more javadoc source/resource artifacts:\n\n" + e.getMessage(), e);
2314         }
2315     }
2316 
2317     /**
2318      * Returns an ArtifactFilter that only includes direct dependencies of this project
2319      * (verified via groupId and artifactId).
2320      */
2321     private TransformableFilter createDependencyArtifactFilter() {
2322         Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
2323 
2324         List<String> artifactPatterns = new ArrayList<>(dependencyArtifacts.size());
2325         for (Artifact artifact : dependencyArtifacts) {
2326             artifactPatterns.add(artifact.getGroupId() + ":" + artifact.getArtifactId());
2327         }
2328 
2329         return new PatternInclusionsFilter(artifactPatterns);
2330     }
2331 
2332     /**
2333      * Construct a SourceResolverConfig for resolving dependency sources and resources in a consistent
2334      * way, so it can be reused for both source and resource resolution.
2335      *
2336      * @since 2.7
2337      */
2338     private SourceResolverConfig getDependencySourceResolverConfig() {
2339         final List<TransformableFilter> andFilters = new ArrayList<>();
2340 
2341         final List<String> dependencyIncludes = dependencySourceIncludes;
2342         final List<String> dependencyExcludes = dependencySourceExcludes;
2343 
2344         if (!includeTransitiveDependencySources || isNotEmpty(dependencyIncludes) || isNotEmpty(dependencyExcludes)) {
2345             if (!includeTransitiveDependencySources) {
2346                 andFilters.add(createDependencyArtifactFilter());
2347             }
2348 
2349             if (isNotEmpty(dependencyIncludes)) {
2350                 andFilters.add(new PatternInclusionsFilter(dependencyIncludes));
2351             }
2352 
2353             if (isNotEmpty(dependencyExcludes)) {
2354                 andFilters.add(new PatternExclusionsFilter(dependencyExcludes));
2355             }
2356         }
2357 
2358         return configureDependencySourceResolution(
2359                         new SourceResolverConfig(project, getProjectBuildingRequest(project), sourceDependencyCacheDir)
2360                                 .withReactorProjects(this.reactorProjects))
2361                 .withFilter(new AndFilter(andFilters));
2362     }
2363 
2364     private ProjectBuildingRequest getProjectBuildingRequest(MavenProject currentProject) {
2365         return new DefaultProjectBuildingRequest(session.getProjectBuildingRequest())
2366                 .setRemoteRepositories(currentProject.getRemoteArtifactRepositories());
2367     }
2368 
2369     /**
2370      * Method that indicates whether the javadoc can be generated or not. If the project does not contain any source
2371      * files and no subpackages are specified, the plugin will terminate.
2372      *
2373      * @param files the project files
2374      * @return a boolean that indicates whether javadoc report can be generated or not
2375      */
2376     protected boolean canGenerateReport(Map<Path, Collection<String>> files) {
2377         for (Collection<String> filesValues : files.values()) {
2378             if (!filesValues.isEmpty()) {
2379                 return true;
2380             }
2381         }
2382 
2383         return !(subpackages == null || subpackages.isEmpty());
2384     }
2385 
2386     // ----------------------------------------------------------------------
2387     // private methods
2388     // ----------------------------------------------------------------------
2389 
2390     /**
2391      * Method to get the excluded source files from the javadoc and create the argument string
2392      * that will be included in the javadoc commandline execution.
2393      *
2394      * @param sourcePaths the collection of paths to the source files
2395      * @return a String that contains the exclude argument that will be used by javadoc
2396      * @throws MavenReportException
2397      */
2398     private String getExcludedPackages(Collection<Path> sourcePaths) throws MavenReportException {
2399         List<String> excludedNames = null;
2400 
2401         if ((sourcepath != null && !sourcepath.isEmpty()) && (subpackages != null && !subpackages.isEmpty())) {
2402             Collection<String> excludedPackages = getExcludedPackages();
2403 
2404             excludedNames = JavadocUtil.getExcludedPackages(sourcePaths, excludedPackages);
2405         }
2406 
2407         String excludeArg = "";
2408         if ((subpackages != null && !subpackages.isEmpty()) && excludedNames != null) {
2409             // add the excludedpackage names
2410             excludeArg = StringUtils.join(excludedNames.iterator(), ":");
2411         }
2412 
2413         return excludeArg;
2414     }
2415 
2416     /**
2417      * Method to format the specified source paths that will be accepted by the javadoc tool.
2418      *
2419      * @param sourcePaths the list of paths to the source files that will be included in the javadoc.
2420      * @return a String that contains the formatted source path argument, separated by the System pathSeparator
2421      *         string (colon (<code>:</code>) on Solaris or semicolon (<code>;</code>) on Windows).
2422      * @see File#pathSeparator
2423      */
2424     private String getSourcePath(Collection<Path> sourcePaths) {
2425         String sourcePath = null;
2426 
2427         if ((subpackages == null || subpackages.isEmpty()) || (sourcepath != null && !sourcepath.isEmpty())) {
2428             sourcePath = StringUtils.join(sourcePaths.iterator(), File.pathSeparator);
2429         }
2430 
2431         return sourcePath;
2432     }
2433 
2434     /**
2435      * Method to get the packages specified in the <code>excludePackageNames</code> parameter. The packages are split
2436      * with ',', ':', or ';' and then formatted.
2437      *
2438      * @return an array of String objects that contain the package names
2439      * @throws MavenReportException
2440      */
2441     private Collection<String> getExcludedPackages() throws MavenReportException {
2442         Set<String> excluded = new LinkedHashSet<>();
2443 
2444         if (includeDependencySources) {
2445             try {
2446                 resolveDependencyBundles();
2447             } catch (IOException e) {
2448                 throw new MavenReportException(
2449                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
2450             }
2451 
2452             if (isNotEmpty(dependencyJavadocBundles)) {
2453                 for (JavadocBundle bundle : dependencyJavadocBundles) {
2454                     JavadocOptions options = bundle.getOptions();
2455                     if (options != null && isNotEmpty(options.getExcludePackageNames())) {
2456                         excluded.addAll(options.getExcludePackageNames());
2457                     }
2458                 }
2459             }
2460         }
2461 
2462         // for the specified excludePackageNames
2463         if (excludePackageNames != null && !excludePackageNames.isEmpty()) {
2464             List<String> packageNames = Arrays.asList(excludePackageNames.split("[,:;]"));
2465             excluded.addAll(trimValues(packageNames));
2466         }
2467 
2468         return excluded;
2469     }
2470 
2471     private static List<String> trimValues(List<String> items) {
2472         List<String> result = new ArrayList<>(items.size());
2473         for (String item : items) {
2474             String trimmed = item.trim();
2475             if (trimmed == null || trimmed.isEmpty()) {
2476                 continue;
2477             }
2478             result.add(trimmed);
2479         }
2480         return result;
2481     }
2482 
2483     private List<org.eclipse.aether.graph.Dependency> toResolverDependencies(List<Dependency> dependencies) {
2484         if (dependencies == null) {
2485             return null;
2486         }
2487         ArtifactTypeRegistry registry = RepositoryUtils.newArtifactTypeRegistry(artifactHandlerManager);
2488         return dependencies.stream()
2489                 .map(d -> RepositoryUtils.toDependency(d, registry))
2490                 .collect(Collectors.toList());
2491     }
2492 
2493     /**
2494      * Method that gets the classpath and modulepath elements that will be specified in the javadoc
2495      * <code>-classpath</code> and <code>--module-path</code> parameter.
2496      * Since we have all the sources of the current reactor, it is sufficient to consider the
2497      * dependencies of the reactor modules, excluding the module artifacts which may not yet be available
2498      * when the reactor project is built for the first time.
2499      *
2500      * @return all classpath elements
2501      * @throws MavenReportException if any.
2502      */
2503     private Collection<File> getPathElements() throws MavenReportException {
2504         Set<File> classpathElements = new LinkedHashSet<>();
2505         Map<String, Artifact> compileArtifactMap = new LinkedHashMap<>();
2506 
2507         if (isTest()) {
2508             classpathElements.addAll(getProjectBuildOutputDirs(project));
2509         }
2510 
2511         populateCompileArtifactMap(compileArtifactMap, project.getArtifacts());
2512 
2513         if (isAggregator()) {
2514             Collection<MavenProject> aggregatorProjects = getAggregatedProjects();
2515 
2516             List<String> reactorArtifacts = new ArrayList<>();
2517             for (MavenProject p : aggregatorProjects) {
2518                 reactorArtifacts.add(p.getGroupId() + ':' + p.getArtifactId());
2519             }
2520 
2521             DependencyFilter dependencyFilter = new AndDependencyFilter(
2522                     new PatternExclusionsDependencyFilter(reactorArtifacts), getDependencyScopeFilter());
2523 
2524             for (MavenProject subProject : aggregatorProjects) {
2525                 if (subProject != project) {
2526                     File projectArtifactFile = getClassesFile(subProject);
2527                     if (projectArtifactFile != null) {
2528                         classpathElements.add(projectArtifactFile);
2529                     } else {
2530                         classpathElements.addAll(getProjectBuildOutputDirs(subProject));
2531                     }
2532 
2533                     try {
2534                         StringBuilder sb = new StringBuilder();
2535 
2536                         sb.append("Compiled artifacts for ");
2537                         sb.append(subProject.getGroupId()).append(":");
2538                         sb.append(subProject.getArtifactId()).append(":");
2539                         sb.append(subProject.getVersion()).append('\n');
2540 
2541                         List<Dependency> managedDependencies = null;
2542                         if (subProject.getDependencyManagement() != null) {
2543                             managedDependencies =
2544                                     subProject.getDependencyManagement().getDependencies();
2545                         }
2546 
2547                         CollectRequest collRequest = new CollectRequest(
2548                                 toResolverDependencies(subProject.getDependencies()),
2549                                 toResolverDependencies(managedDependencies),
2550                                 subProject.getRemoteProjectRepositories());
2551                         DependencyRequest depRequest = new DependencyRequest(collRequest, dependencyFilter);
2552                         for (ArtifactResult artifactResult : repoSystem
2553                                 .resolveDependencies(repoSession, depRequest)
2554                                 .getArtifactResults()) {
2555                             List<Artifact> artifacts =
2556                                     Collections.singletonList(RepositoryUtils.toArtifact(artifactResult.getArtifact()));
2557                             populateCompileArtifactMap(compileArtifactMap, artifacts);
2558 
2559                             sb.append(artifactResult.getArtifact().getFile()).append('\n');
2560                         }
2561 
2562                         if (getLog().isDebugEnabled()) {
2563                             getLog().debug(sb.toString());
2564                         }
2565 
2566                     } catch (DependencyResolutionException e) {
2567                         throw new MavenReportException(e.getMessage(), e);
2568                     }
2569                 }
2570             }
2571         }
2572 
2573         for (Artifact a : compileArtifactMap.values()) {
2574             classpathElements.add(a.getFile());
2575         }
2576 
2577         if (additionalDependencies != null) {
2578             for (Dependency dependency : additionalDependencies) {
2579                 Artifact artifact = resolveDependency(dependency);
2580                 getLog().debug("add additional artifact with path " + artifact.getFile());
2581                 classpathElements.add(artifact.getFile());
2582             }
2583         }
2584 
2585         return classpathElements;
2586     }
2587 
2588     protected ScopeDependencyFilter getDependencyScopeFilter() {
2589         return new ScopeDependencyFilter(
2590                 Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_SYSTEM), null);
2591     }
2592 
2593     /**
2594      * @param dependency {@link Dependency}
2595      * @return {@link Artifact}
2596      * @throws MavenReportException when artifact could not be resolved
2597      */
2598     public Artifact resolveDependency(Dependency dependency) throws MavenReportException {
2599         ArtifactTypeRegistry registry = RepositoryUtils.newArtifactTypeRegistry(artifactHandlerManager);
2600         ArtifactRequest req = new ArtifactRequest(
2601                 RepositoryUtils.toDependency(dependency, registry).getArtifact(),
2602                 project.getRemoteProjectRepositories(),
2603                 null);
2604         try {
2605             ArtifactResult resolutionResult = repoSystem.resolveArtifact(repoSession, req);
2606             return RepositoryUtils.toArtifact(resolutionResult.getArtifact());
2607         } catch (ArtifactResolutionException e) {
2608             throw new MavenReportException("artifact resolver problem - " + e.getMessage(), e);
2609         }
2610     }
2611 
2612     protected final Toolchain getToolchain() {
2613         Toolchain tc = null;
2614 
2615         if (jdkToolchain != null) {
2616             List<Toolchain> tcs = toolchainManager.getToolchains(session, "jdk", jdkToolchain);
2617             if (tcs != null && !tcs.isEmpty()) {
2618                 tc = tcs.get(0);
2619             }
2620         }
2621 
2622         if (tc == null) {
2623             tc = toolchainManager.getToolchainFromBuildContext("jdk", session);
2624         }
2625 
2626         return tc;
2627     }
2628 
2629     /**
2630      * Method to put the artifacts in the hashmap.
2631      *
2632      * @param compileArtifactMap the hashmap that will contain the artifacts
2633      * @param artifactList       the list of artifacts that will be put in the map
2634      * @throws MavenReportException if any
2635      */
2636     private void populateCompileArtifactMap(Map<String, Artifact> compileArtifactMap, Collection<Artifact> artifactList)
2637             throws MavenReportException {
2638         if (artifactList == null) {
2639             return;
2640         }
2641 
2642         for (Artifact newArtifact : artifactList) {
2643             File file = newArtifact.getFile();
2644 
2645             if (file == null) {
2646                 throw new MavenReportException(
2647                         "Error in plugin descriptor - " + "dependency was not resolved for artifact: "
2648                                 + newArtifact.getGroupId() + ":" + newArtifact.getArtifactId() + ":"
2649                                 + newArtifact.getVersion());
2650             }
2651 
2652             if (compileArtifactMap.get(newArtifact.getDependencyConflictId()) != null) {
2653                 Artifact oldArtifact = compileArtifactMap.get(newArtifact.getDependencyConflictId());
2654 
2655                 ArtifactVersion oldVersion = new DefaultArtifactVersion(oldArtifact.getVersion());
2656                 ArtifactVersion newVersion = new DefaultArtifactVersion(newArtifact.getVersion());
2657                 if (newVersion.compareTo(oldVersion) > 0) {
2658                     compileArtifactMap.put(newArtifact.getDependencyConflictId(), newArtifact);
2659                 }
2660             } else {
2661                 compileArtifactMap.put(newArtifact.getDependencyConflictId(), newArtifact);
2662             }
2663         }
2664     }
2665 
2666     /**
2667      * Method that sets the text that will be displayed on the bottom of the javadocs.
2668      *
2669      * @return a String that contains the text that will be displayed at the bottom of the javadoc
2670      */
2671     private String getBottomText() {
2672         if (this.bottom == null) {
2673             return null;
2674         }
2675 
2676         final String inceptionYear = project.getInceptionYear();
2677 
2678         // get Reproducible Builds outputTimestamp date value or the current local date.
2679         final LocalDate localDate = MavenArchiver.parseBuildOutputTimestamp(outputTimestamp)
2680                 .map(instant -> instant.atZone(ZoneOffset.UTC).toLocalDate())
2681                 .orElseGet(LocalDate::now);
2682 
2683         final String currentYear = Integer.toString(localDate.getYear());
2684 
2685         String theBottom = this.bottom.replace("{currentYear}", currentYear);
2686 
2687         if ((inceptionYear == null) || inceptionYear.equals(currentYear)) {
2688             theBottom = theBottom.replace("{inceptionYear}&#x2013;", "");
2689         } else {
2690             theBottom = theBottom.replace("{inceptionYear}", inceptionYear);
2691         }
2692 
2693         if (project.getOrganization() == null) {
2694             theBottom = theBottom.replace(" {organizationName}", "");
2695         } else {
2696             if (StringUtils.isNotEmpty(project.getOrganization().getName())) {
2697                 if (StringUtils.isNotEmpty(project.getOrganization().getUrl())) {
2698                     theBottom = theBottom.replace(
2699                             "{organizationName}",
2700                             "<a href=\"" + project.getOrganization().getUrl() + "\">"
2701                                     + project.getOrganization().getName() + "</a>");
2702                 } else {
2703                     theBottom = theBottom.replace(
2704                             "{organizationName}", project.getOrganization().getName());
2705                 }
2706             } else {
2707                 theBottom = theBottom.replace(" {organizationName}", "");
2708             }
2709         }
2710 
2711         return theBottom;
2712     }
2713 
2714     /**
2715      * Method to get the stylesheet path file to be used by the Javadoc Tool.
2716      * <br/>
2717      * If the {@link #stylesheetfile} is empty, return the file as String defined by {@link #stylesheet} value.
2718      * <br/>
2719      * If the {@link #stylesheetfile} is defined, return the file as String.
2720      * <br/>
2721      * Note: since 2.6, the {@link #stylesheetfile} could be a path from a resource in the project source
2722      * directories (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
2723      * or from a resource in the Javadoc plugin dependencies.
2724      *
2725      * @param javadocOutputDirectory the output directory
2726      * @return the stylesheet file absolute path as String.
2727      * @see #getResource(List, String)
2728      */
2729     private Optional<File> getStylesheetFile(final File javadocOutputDirectory) {
2730         if (stylesheetfile == null || stylesheetfile.isEmpty()) {
2731             if ("java".equalsIgnoreCase(stylesheet)) {
2732                 // use the default Javadoc tool stylesheet
2733                 return Optional.empty();
2734             }
2735 
2736             getLog().warn("Parameter 'stylesheet' is no longer evaluated, rather use 'addStylesheets'"
2737                     + " to customize the CSS!");
2738             return Optional.empty();
2739         }
2740 
2741         if (new File(stylesheetfile).exists()) {
2742             return Optional.of(new File(stylesheetfile));
2743         }
2744         if (JavaVersion.JAVA_VERSION.isAtLeast("23")) {
2745             return getResource(
2746                     new File(new File(javadocOutputDirectory, "resource-files"), DEFAULT_CSS_NAME), stylesheetfile);
2747         }
2748         return getResource(new File(javadocOutputDirectory, DEFAULT_CSS_NAME), stylesheetfile);
2749     }
2750 
2751     private void addAddStyleSheets(List<String> arguments) throws MavenReportException {
2752         if (addStylesheets == null) {
2753             return;
2754         }
2755 
2756         for (String addStylesheet : addStylesheets) {
2757             Optional<File> styleSheet = getAddStylesheet(getJavadocDirectory(), addStylesheet);
2758 
2759             if (styleSheet.isPresent()) {
2760                 addArgIfNotEmpty(
2761                         arguments,
2762                         "--add-stylesheet",
2763                         JavadocUtil.quotedPathArgument(styleSheet.get().getAbsolutePath()),
2764                         JavaVersion.parse("10"));
2765             }
2766         }
2767     }
2768 
2769     private Optional<File> getAddStylesheet(final File javadocOutputDirectory, final String stylesheet)
2770             throws MavenReportException {
2771         if (stylesheet == null || stylesheet.isEmpty()) {
2772             return Optional.empty();
2773         }
2774 
2775         File addstylesheetfile = new File(getJavadocDirectory(), stylesheet);
2776         if (addstylesheetfile.exists()) {
2777             Optional<File> stylesheetfile = getStylesheetFile(javadocOutputDirectory);
2778             if (stylesheetfile.isPresent()) {
2779                 if (stylesheetfile.get().getName().equals(addstylesheetfile.getName())) {
2780                     throw new MavenReportException("additional stylesheet must have a different name "
2781                             + "than stylesheetfile: " + stylesheetfile.get().getName());
2782                 }
2783             }
2784 
2785             return Optional.of(addstylesheetfile);
2786         }
2787 
2788         throw new MavenReportException(
2789                 "additional stylesheet file does not exist: " + addstylesheetfile.getAbsolutePath());
2790     }
2791 
2792     /**
2793      * Method to get the help file to be used by the Javadoc Tool.
2794      * <br/>
2795      * Since 2.6, the {@code helpfile} could be a path from a resource in the project source
2796      * directories (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
2797      * or from a resource in the Javadoc plugin dependencies.
2798      *
2799      * @param javadocOutputDirectory the output directory.
2800      * @return the help file absolute path as String.
2801      * @see #getResource(File, String)
2802      * @since 2.6
2803      */
2804     private Optional<File> getHelpFile(final File javadocOutputDirectory) {
2805         if (helpfile == null || helpfile.isEmpty()) {
2806             return Optional.empty();
2807         }
2808 
2809         if (new File(helpfile).exists()) {
2810             return Optional.of(new File(helpfile));
2811         }
2812 
2813         return getResource(new File(javadocOutputDirectory, "help-doc.html"), helpfile);
2814     }
2815 
2816     /**
2817      * Method to get the access level for the classes and members to be shown in the generated javadoc.
2818      * If the specified access level is not public, protected, package or private, the access level
2819      * is set to protected.
2820      *
2821      * @return the access level
2822      */
2823     private String getAccessLevel() {
2824         String accessLevel;
2825         if ("public".equalsIgnoreCase(show)
2826                 || "protected".equalsIgnoreCase(show)
2827                 || "package".equalsIgnoreCase(show)
2828                 || "private".equalsIgnoreCase(show)) {
2829             accessLevel = "-" + show;
2830         } else {
2831             if (getLog().isErrorEnabled()) {
2832                 getLog().error("Unrecognized access level to show '" + show + "'. Defaulting to protected.");
2833             }
2834             accessLevel = "-protected";
2835         }
2836 
2837         return accessLevel;
2838     }
2839 
2840     /**
2841      * Method to get the path of the bootclass artifacts used in the <code>-bootclasspath</code> option.
2842      *
2843      * @return a string that contains bootclass path, separated by the System pathSeparator string
2844      *         (colon (<code>:</code>) on Solaris or semicolon (<code>;</code>) on Windows).
2845      * @throws MavenReportException if any
2846      * @see File#pathSeparator
2847      */
2848     private String getBootclassPath() throws MavenReportException {
2849         Set<BootclasspathArtifact> bootclasspathArtifacts = collectBootClasspathArtifacts();
2850 
2851         List<String> bootclassPath = new ArrayList<>();
2852         for (BootclasspathArtifact aBootclasspathArtifact : bootclasspathArtifacts) {
2853             if ((StringUtils.isNotEmpty(aBootclasspathArtifact.getGroupId()))
2854                     && (StringUtils.isNotEmpty(aBootclasspathArtifact.getArtifactId()))
2855                     && (StringUtils.isNotEmpty(aBootclasspathArtifact.getVersion()))) {
2856                 bootclassPath.addAll(getArtifactsAbsolutePath(aBootclasspathArtifact));
2857             }
2858         }
2859 
2860         bootclassPath = JavadocUtil.pruneFiles(bootclassPath);
2861 
2862         StringBuilder path = new StringBuilder();
2863         path.append(StringUtils.join(bootclassPath.iterator(), File.pathSeparator));
2864 
2865         if (bootclasspath != null && !bootclasspath.isEmpty()) {
2866             path.append(JavadocUtil.unifyPathSeparator(bootclasspath));
2867         }
2868 
2869         return path.toString();
2870     }
2871 
2872     /**
2873      * Method to get the path of the doclet artifacts used in the <code>-docletpath</code> option.
2874      * <p/>
2875      * Either docletArtifact or doclectArtifacts can be defined and used, not both, docletArtifact
2876      * takes precedence over doclectArtifacts. docletPath is always appended to any result path
2877      * definition.
2878      *
2879      * @return a string that contains doclet path, separated by the System pathSeparator string
2880      *         (colon (<code>:</code>) on Solaris or semicolon (<code>;</code>) on Windows).
2881      * @throws MavenReportException if any
2882      * @see File#pathSeparator
2883      */
2884     private String getDocletPath() throws MavenReportException {
2885         Set<DocletArtifact> docletArtifacts = collectDocletArtifacts();
2886         List<String> pathParts = new ArrayList<>();
2887 
2888         for (DocletArtifact docletArtifact : docletArtifacts) {
2889             if (!isDocletArtifactEmpty(docletArtifact)) {
2890                 pathParts.addAll(getArtifactsAbsolutePath(docletArtifact));
2891             }
2892         }
2893 
2894         if (!(docletPath == null || docletPath.isEmpty())) {
2895             pathParts.add(JavadocUtil.unifyPathSeparator(docletPath));
2896         }
2897 
2898         String path = StringUtils.join(pathParts.iterator(), File.pathSeparator);
2899 
2900         if ((path == null || path.isEmpty()) && getLog().isWarnEnabled()) {
2901             getLog().warn("No docletpath option was found. Please review <docletpath/> or <docletArtifact/>"
2902                     + " or <doclets/>.");
2903         }
2904 
2905         return path;
2906     }
2907 
2908     /**
2909      * Verify if a doclet artifact is empty or not
2910      *
2911      * @param aDocletArtifact could be null
2912      * @return <code>true</code> if aDocletArtifact or the groupId/artifactId/version of the doclet artifact is null,
2913      *         <code>false</code> otherwise.
2914      */
2915     private boolean isDocletArtifactEmpty(DocletArtifact aDocletArtifact) {
2916         if (aDocletArtifact == null) {
2917             return true;
2918         }
2919 
2920         return StringUtils.isEmpty(aDocletArtifact.getGroupId())
2921                 && StringUtils.isEmpty(aDocletArtifact.getArtifactId())
2922                 && StringUtils.isEmpty(aDocletArtifact.getVersion());
2923     }
2924 
2925     /**
2926      * Method to get the path of the taglet artifacts used in the <code>-tagletpath</code> option.
2927      *
2928      * @return a string that contains taglet path, separated by the System pathSeparator string
2929      *         (colon (<code>:</code>) on Solaris or semicolon (<code>;</code>) on Windows).
2930      * @throws MavenReportException if any
2931      * @see File#pathSeparator
2932      */
2933     private String getTagletPath() throws MavenReportException {
2934         Set<TagletArtifact> tArtifacts = collectTagletArtifacts();
2935         Collection<String> pathParts = new ArrayList<>();
2936 
2937         for (TagletArtifact tagletArtifact : tArtifacts) {
2938             if ((tagletArtifact != null)
2939                     && (StringUtils.isNotEmpty(tagletArtifact.getGroupId()))
2940                     && (StringUtils.isNotEmpty(tagletArtifact.getArtifactId()))
2941                     && (StringUtils.isNotEmpty(tagletArtifact.getVersion()))) {
2942                 pathParts.addAll(getArtifactsAbsolutePath(tagletArtifact));
2943             }
2944         }
2945 
2946         Set<Taglet> taglets = collectTaglets();
2947         for (Taglet taglet : taglets) {
2948             if (taglet == null) {
2949                 continue;
2950             }
2951 
2952             if ((taglet.getTagletArtifact() != null)
2953                     && (StringUtils.isNotEmpty(taglet.getTagletArtifact().getGroupId()))
2954                     && (StringUtils.isNotEmpty(taglet.getTagletArtifact().getArtifactId()))
2955                     && (StringUtils.isNotEmpty(taglet.getTagletArtifact().getVersion()))) {
2956                 pathParts.addAll(JavadocUtil.pruneFiles(getArtifactsAbsolutePath(taglet.getTagletArtifact())));
2957             } else if (StringUtils.isNotEmpty(taglet.getTagletpath())) {
2958                 for (Path path :
2959                         JavadocUtil.prunePaths(project, Collections.singletonList(taglet.getTagletpath()), true)) {
2960                     pathParts.add(path.toString());
2961                 }
2962             }
2963         }
2964 
2965         if (StringUtils.isNotEmpty(tagletpath)) {
2966             pathParts.addAll(Arrays.asList(JavadocUtil.splitPath(tagletpath)));
2967         }
2968 
2969         return StringUtils.join(pathParts, File.pathSeparator);
2970     }
2971 
2972     private Set<String> collectLinks() throws MavenReportException {
2973         Set<String> links = new LinkedHashSet<>();
2974 
2975         if (includeDependencySources) {
2976             try {
2977                 resolveDependencyBundles();
2978             } catch (IOException e) {
2979                 throw new MavenReportException(
2980                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
2981             }
2982 
2983             if (isNotEmpty(dependencyJavadocBundles)) {
2984                 for (JavadocBundle bundle : dependencyJavadocBundles) {
2985                     JavadocOptions options = bundle.getOptions();
2986                     if (options != null && isNotEmpty(options.getLinks())) {
2987                         links.addAll(options.getLinks());
2988                     }
2989                 }
2990             }
2991         }
2992 
2993         if (isNotEmpty(this.links)) {
2994             links.addAll(this.links);
2995         }
2996 
2997         links.addAll(getDependenciesLinks());
2998 
2999         return followLinks(links);
3000     }
3001 
3002     private Set<Group> collectGroups() throws MavenReportException {
3003         Set<Group> groups = new LinkedHashSet<>();
3004 
3005         if (includeDependencySources) {
3006             try {
3007                 resolveDependencyBundles();
3008             } catch (IOException e) {
3009                 throw new MavenReportException(
3010                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3011             }
3012 
3013             if (isNotEmpty(dependencyJavadocBundles)) {
3014                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3015                     JavadocOptions options = bundle.getOptions();
3016                     if (options != null && isNotEmpty(options.getGroups())) {
3017                         groups.addAll(options.getGroups());
3018                     }
3019                 }
3020             }
3021         }
3022 
3023         if (this.groups != null && this.groups.length > 0) {
3024             groups.addAll(Arrays.asList(this.groups));
3025         }
3026 
3027         return groups;
3028     }
3029 
3030     private Set<ResourcesArtifact> collectResourcesArtifacts() throws MavenReportException {
3031         Set<ResourcesArtifact> result = new LinkedHashSet<>();
3032 
3033         if (includeDependencySources) {
3034             try {
3035                 resolveDependencyBundles();
3036             } catch (IOException e) {
3037                 throw new MavenReportException(
3038                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3039             }
3040 
3041             if (isNotEmpty(dependencyJavadocBundles)) {
3042                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3043                     JavadocOptions options = bundle.getOptions();
3044                     if (options != null && isNotEmpty(options.getResourcesArtifacts())) {
3045                         result.addAll(options.getResourcesArtifacts());
3046                     }
3047                 }
3048             }
3049         }
3050 
3051         if (this.resourcesArtifacts != null && this.resourcesArtifacts.length > 0) {
3052             result.addAll(Arrays.asList(this.resourcesArtifacts));
3053         }
3054 
3055         return result;
3056     }
3057 
3058     private Set<BootclasspathArtifact> collectBootClasspathArtifacts() throws MavenReportException {
3059         Set<BootclasspathArtifact> result = new LinkedHashSet<>();
3060 
3061         if (includeDependencySources) {
3062             try {
3063                 resolveDependencyBundles();
3064             } catch (IOException e) {
3065                 throw new MavenReportException(
3066                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3067             }
3068 
3069             if (isNotEmpty(dependencyJavadocBundles)) {
3070                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3071                     JavadocOptions options = bundle.getOptions();
3072                     if (options != null && isNotEmpty(options.getBootclasspathArtifacts())) {
3073                         result.addAll(options.getBootclasspathArtifacts());
3074                     }
3075                 }
3076             }
3077         }
3078 
3079         if (this.bootclasspathArtifacts != null && this.bootclasspathArtifacts.length > 0) {
3080             result.addAll(Arrays.asList(this.bootclasspathArtifacts));
3081         }
3082 
3083         return result;
3084     }
3085 
3086     private Set<OfflineLink> collectOfflineLinks() throws MavenReportException {
3087         Set<OfflineLink> result = new LinkedHashSet<>();
3088 
3089         OfflineLink javaApiLink = getDefaultJavadocApiLink();
3090         if (javaApiLink != null) {
3091             result.add(javaApiLink);
3092         }
3093 
3094         if (includeDependencySources) {
3095             try {
3096                 resolveDependencyBundles();
3097             } catch (IOException e) {
3098                 throw new MavenReportException(
3099                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3100             }
3101 
3102             if (isNotEmpty(dependencyJavadocBundles)) {
3103                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3104                     JavadocOptions options = bundle.getOptions();
3105                     if (options != null && isNotEmpty(options.getOfflineLinks())) {
3106                         result.addAll(options.getOfflineLinks());
3107                     }
3108                 }
3109             }
3110         }
3111 
3112         if (this.offlineLinks != null && this.offlineLinks.length > 0) {
3113             result.addAll(Arrays.asList(this.offlineLinks));
3114         }
3115 
3116         return result;
3117     }
3118 
3119     private Set<Tag> collectTags() throws MavenReportException {
3120         Set<Tag> tags = new LinkedHashSet<>();
3121 
3122         if (includeDependencySources) {
3123             try {
3124                 resolveDependencyBundles();
3125             } catch (IOException e) {
3126                 throw new MavenReportException(
3127                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3128             }
3129 
3130             if (isNotEmpty(dependencyJavadocBundles)) {
3131                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3132                     JavadocOptions options = bundle.getOptions();
3133                     if (options != null && isNotEmpty(options.getTags())) {
3134                         tags.addAll(options.getTags());
3135                     }
3136                 }
3137             }
3138         }
3139 
3140         if (this.tags != null && this.tags.length > 0) {
3141             tags.addAll(Arrays.asList(this.tags));
3142         }
3143 
3144         return tags;
3145     }
3146 
3147     private Set<TagletArtifact> collectTagletArtifacts() throws MavenReportException {
3148         Set<TagletArtifact> tArtifacts = new LinkedHashSet<>();
3149 
3150         if (includeDependencySources) {
3151             try {
3152                 resolveDependencyBundles();
3153             } catch (IOException e) {
3154                 throw new MavenReportException(
3155                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3156             }
3157 
3158             if (isNotEmpty(dependencyJavadocBundles)) {
3159                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3160                     JavadocOptions options = bundle.getOptions();
3161                     if (options != null && isNotEmpty(options.getTagletArtifacts())) {
3162                         tArtifacts.addAll(options.getTagletArtifacts());
3163                     }
3164                 }
3165             }
3166         }
3167 
3168         if (tagletArtifact != null) {
3169             tArtifacts.add(tagletArtifact);
3170         }
3171 
3172         if (tagletArtifacts != null && tagletArtifacts.length > 0) {
3173             tArtifacts.addAll(Arrays.asList(tagletArtifacts));
3174         }
3175 
3176         return tArtifacts;
3177     }
3178 
3179     private Set<DocletArtifact> collectDocletArtifacts() throws MavenReportException {
3180         Set<DocletArtifact> dArtifacts = new LinkedHashSet<>();
3181 
3182         if (includeDependencySources) {
3183             try {
3184                 resolveDependencyBundles();
3185             } catch (IOException e) {
3186                 throw new MavenReportException(
3187                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3188             }
3189 
3190             if (isNotEmpty(dependencyJavadocBundles)) {
3191                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3192                     JavadocOptions options = bundle.getOptions();
3193                     if (options != null && isNotEmpty(options.getDocletArtifacts())) {
3194                         dArtifacts.addAll(options.getDocletArtifacts());
3195                     }
3196                 }
3197             }
3198         }
3199 
3200         if (docletArtifact != null) {
3201             dArtifacts.add(docletArtifact);
3202         }
3203 
3204         if (docletArtifacts != null && docletArtifacts.length > 0) {
3205             dArtifacts.addAll(Arrays.asList(docletArtifacts));
3206         }
3207 
3208         return dArtifacts;
3209     }
3210 
3211     private Set<Taglet> collectTaglets() throws MavenReportException {
3212         Set<Taglet> result = new LinkedHashSet<>();
3213 
3214         if (includeDependencySources) {
3215             try {
3216                 resolveDependencyBundles();
3217             } catch (IOException e) {
3218                 throw new MavenReportException(
3219                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
3220             }
3221 
3222             if (isNotEmpty(dependencyJavadocBundles)) {
3223                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3224                     JavadocOptions options = bundle.getOptions();
3225                     if (options != null && isNotEmpty(options.getTaglets())) {
3226                         result.addAll(options.getTaglets());
3227                     }
3228                 }
3229             }
3230         }
3231 
3232         if (taglets != null && taglets.length > 0) {
3233             result.addAll(Arrays.asList(taglets));
3234         }
3235 
3236         return result;
3237     }
3238 
3239     /**
3240      * Return the Javadoc artifact path and its transitive dependencies path from the local repository
3241      *
3242      * @param javadocArtifact not null
3243      * @return a list of locale artifacts absolute path
3244      * @throws MavenReportException if any
3245      */
3246     private List<String> getArtifactsAbsolutePath(JavadocPathArtifact javadocArtifact) throws MavenReportException {
3247         if ((StringUtils.isEmpty(javadocArtifact.getGroupId()))
3248                 && (StringUtils.isEmpty(javadocArtifact.getArtifactId()))
3249                 && (StringUtils.isEmpty(javadocArtifact.getVersion()))) {
3250             return Collections.emptyList();
3251         }
3252 
3253         List<String> path = new ArrayList<>();
3254 
3255         try {
3256             Artifact artifact = createAndResolveArtifact(javadocArtifact);
3257             path.add(artifact.getFile().getAbsolutePath());
3258 
3259             DependencyFilter filter = new ScopeDependencyFilter(
3260                     Arrays.asList(Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED), Collections.emptySet());
3261             DependencyRequest req = new DependencyRequest(
3262                     new CollectRequest(
3263                             new org.eclipse.aether.graph.Dependency(RepositoryUtils.toArtifact(artifact), null),
3264                             RepositoryUtils.toRepos(project.getRemoteArtifactRepositories())),
3265                     filter);
3266             Iterable<ArtifactResult> deps =
3267                     repoSystem.resolveDependencies(repoSession, req).getArtifactResults();
3268             for (ArtifactResult a : deps) {
3269                 path.add(a.getArtifact().getFile().getAbsolutePath());
3270             }
3271 
3272             return path;
3273         } catch (ArtifactResolutionException e) {
3274             throw new MavenReportException("Unable to resolve artifact:" + javadocArtifact, e);
3275         } catch (DependencyResolutionException e) {
3276             throw new MavenReportException("Unable to resolve dependencies for:" + javadocArtifact, e);
3277         }
3278     }
3279 
3280     /**
3281      * Creates an {@link Artifact} representing the configured {@link JavadocPathArtifact} and resolves it.
3282      *
3283      * @param javadocArtifact the {@link JavadocPathArtifact} to resolve
3284      * @return a resolved {@link Artifact}
3285      * @throws org.eclipse.aether.resolution.ArtifactResolutionException issue while resolving artifact
3286      */
3287     private Artifact createAndResolveArtifact(JavadocPathArtifact javadocArtifact)
3288             throws org.eclipse.aether.resolution.ArtifactResolutionException {
3289         org.eclipse.aether.artifact.Artifact artifact = new DefaultArtifact(
3290                 javadocArtifact.getGroupId(),
3291                 javadocArtifact.getArtifactId(),
3292                 javadocArtifact.getClassifier(),
3293                 "jar",
3294                 javadocArtifact.getVersion());
3295         ArtifactRequest req = new ArtifactRequest(artifact, project.getRemoteProjectRepositories(), null);
3296         return RepositoryUtils.toArtifact(
3297                 repoSystem.resolveArtifact(repoSession, req).getArtifact());
3298     }
3299 
3300     /**
3301      * Method that adds/sets the java memory parameters in the command line execution.
3302      *
3303      * @param cmd    the command line execution object where the argument will be added
3304      * @param arg    the argument parameter name
3305      * @param memory the JVM memory value to be set
3306      * @see JavadocUtil#parseJavadocMemory(String)
3307      */
3308     private void addMemoryArg(Commandline cmd, String arg, String memory) {
3309         if (memory != null && !memory.isEmpty()) {
3310             try {
3311                 cmd.createArg().setValue("-J" + arg + JavadocUtil.parseJavadocMemory(memory));
3312             } catch (IllegalArgumentException e) {
3313                 if (getLog().isErrorEnabled()) {
3314                     getLog().error("Malformed memory pattern for '" + arg + memory + "'. Ignore this option.");
3315                 }
3316             }
3317         }
3318     }
3319 
3320     /**
3321      * Method that adds/sets the javadoc proxy parameters in the command line execution.
3322      *
3323      * @param cmd the command line execution object where the argument will be added
3324      */
3325     private void addProxyArg(Commandline cmd) {
3326         if (settings == null || settings.getProxies().isEmpty()) {
3327             return;
3328         }
3329 
3330         Map<String, Proxy> activeProxies = new HashMap<>();
3331 
3332         for (Proxy proxy : settings.getProxies()) {
3333             if (proxy.isActive()) {
3334                 String protocol = proxy.getProtocol();
3335 
3336                 if (!activeProxies.containsKey(protocol)) {
3337                     activeProxies.put(protocol, proxy);
3338                 }
3339             }
3340         }
3341 
3342         if (activeProxies.containsKey("https")) {
3343             Proxy httpsProxy = activeProxies.get("https");
3344             if (StringUtils.isNotEmpty(httpsProxy.getHost())) {
3345                 cmd.createArg().setValue("-J-Dhttps.proxyHost=" + httpsProxy.getHost());
3346                 cmd.createArg().setValue("-J-Dhttps.proxyPort=" + httpsProxy.getPort());
3347 
3348                 if (StringUtils.isNotEmpty(httpsProxy.getNonProxyHosts())
3349                         && (!activeProxies.containsKey("http")
3350                                 || StringUtils.isEmpty(activeProxies.get("http").getNonProxyHosts()))) {
3351                     cmd.createArg()
3352                             .setValue("-J-Dhttp.nonProxyHosts=\""
3353                                     + httpsProxy.getNonProxyHosts().replace("|", "^|") + "\"");
3354                 }
3355             }
3356         }
3357 
3358         if (activeProxies.containsKey("http")) {
3359             Proxy httpProxy = activeProxies.get("http");
3360             if (StringUtils.isNotEmpty(httpProxy.getHost())) {
3361                 cmd.createArg().setValue("-J-Dhttp.proxyHost=" + httpProxy.getHost());
3362                 cmd.createArg().setValue("-J-Dhttp.proxyPort=" + httpProxy.getPort());
3363 
3364                 if (!activeProxies.containsKey("https")) {
3365                     cmd.createArg().setValue("-J-Dhttps.proxyHost=" + httpProxy.getHost());
3366                     cmd.createArg().setValue("-J-Dhttps.proxyPort=" + httpProxy.getPort());
3367                 }
3368 
3369                 if (StringUtils.isNotEmpty(httpProxy.getNonProxyHosts())) {
3370                     cmd.createArg()
3371                             .setValue("-J-Dhttp.nonProxyHosts=\""
3372                                     + httpProxy.getNonProxyHosts().replace("|", "^|") + "\"");
3373                 }
3374             }
3375         }
3376 
3377         // We bravely ignore FTP because no one (probably) uses FTP for Javadoc
3378     }
3379 
3380     /**
3381      * Get the path of the Javadoc tool executable depending on the user entry or try to find it depending on the OS
3382      * or the <code>java.home</code> system property or the <code>JAVA_HOME</code> environment variable.
3383      *
3384      * @return the path of the Javadoc tool
3385      * @throws IOException if not found
3386      */
3387     private String getJavadocExecutable() throws IOException {
3388         Toolchain tc = getToolchain();
3389 
3390         if (tc != null) {
3391             getLog().info("Toolchain in maven-javadoc-plugin: " + tc);
3392             if (javadocExecutable != null) {
3393                 getLog().warn("Toolchains are ignored, 'javadocExecutable' parameter is set to " + javadocExecutable);
3394             } else {
3395                 javadocExecutable = tc.findTool("javadoc");
3396             }
3397         }
3398 
3399         String javadocCommand = "javadoc" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "");
3400 
3401         File javadocExe;
3402 
3403         // ----------------------------------------------------------------------
3404         // The javadoc executable is defined by the user
3405         // ----------------------------------------------------------------------
3406         if (javadocExecutable != null && !javadocExecutable.isEmpty()) {
3407             javadocExe = new File(javadocExecutable);
3408 
3409             if (javadocExe.isDirectory()) {
3410                 javadocExe = new File(javadocExe, javadocCommand);
3411             }
3412 
3413             if (SystemUtils.IS_OS_WINDOWS && javadocExe.getName().indexOf('.') < 0) {
3414                 javadocExe = new File(javadocExe.getPath() + ".exe");
3415             }
3416 
3417             if (!javadocExe.isFile()) {
3418                 throw new IOException("The javadoc executable '" + javadocExe
3419                         + "' doesn't exist or is not a file. Verify the <javadocExecutable/> parameter.");
3420             }
3421 
3422             return javadocExe.getAbsolutePath();
3423         }
3424         // CHECKSTYLE_OFF: LineLength
3425         // ----------------------------------------------------------------------
3426         // Try to find javadocExe from System.getProperty( "java.home" )
3427         // "java.home" is the "Installation directory for Java Runtime
3428         // Environment (JRE)" used to run this code. I.e. it is the parent of
3429         // the directory containing the `java` command used to start this
3430         // application. It does not necessarily have any relation to the
3431         // environment variable JAVA_HOME.
3432         //
3433         // In Java 8 and below the JRE is separate from the JDK. When
3434         // installing the JDK to my-dir/ the javadoc command is installed in
3435         // my-dir/bin/javadoc, the JRE is installed to my-dir/jre, and hence
3436         // the java command is installed to my-dir/jre/bin/java. In this
3437         // configuration "java.home" is mydir/jre/, threfore the relative path
3438         // to the javadoc command is ../bin/javadoc.
3439         //
3440         // In Java 9 and above the JRE is no longer in a subdirectory of the
3441         // JDK, i.e. the JRE and the JDK are merged. In this case the java
3442         // command is installed to my-dir/bin/java alongside the javadoc
3443         // command. So the relative path from "java.home" to the javadoc
3444         // command is bin/javadoc.
3445         //
3446         // References
3447         //
3448         // "System Properties" in "The Java Tutorials"
3449         // https://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html
3450         //
3451         // "Changes to the Installed JDK/JRE Image" in "JDK 9 Migration Guide"
3452         // https://docs.oracle.com/javase/9/migrate/toc.htm?xd_co_f=122a7174-9132-4acd-b122-fac02f8c4fef#JSMIG-GUID-D867DCCC-CEB5-4AFA-9D11-9C62B7A3FAB1
3453         //
3454         // "JEP 220: Modular Run-Time Images"
3455         // http://openjdk.java.net/jeps/220
3456         // ----------------------------------------------------------------------
3457         // CHECKSTYLE_ON: LineLength
3458 
3459         // For IBM's JDK 1.2
3460         if (SystemUtils.IS_OS_AIX) {
3461             javadocExe =
3462                     new File(SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "sh", javadocCommand);
3463         }
3464         // For Apple's JDK 1.6.x (and older?) on Mac OSX
3465         // CHECKSTYLE_OFF: MagicNumber
3466         else if (SystemUtils.IS_OS_MAC_OSX && !JavaVersion.JAVA_SPECIFICATION_VERSION.isAtLeast("1.7"))
3467         // CHECKSTYLE_ON: MagicNumber
3468         {
3469             javadocExe = new File(SystemUtils.getJavaHome() + File.separator + "bin", javadocCommand);
3470         } else if (isJavaVersionAtLeast(org.apache.commons.lang3.JavaVersion.JAVA_9)) {
3471             javadocExe = new File(SystemUtils.getJavaHome() + File.separator + "bin", javadocCommand);
3472         } else {
3473             // Java <= 8
3474             javadocExe = new File(
3475                     SystemUtils.getJavaHome() + File.separator + ".." + File.separator + "bin", javadocCommand);
3476         }
3477 
3478         // ----------------------------------------------------------------------
3479         // Try to find javadocExe from JAVA_HOME environment variable
3480         // ----------------------------------------------------------------------
3481         if (!javadocExe.exists() || !javadocExe.isFile()) {
3482             Properties env = CommandLineUtils.getSystemEnvVars();
3483             String javaHome = env.getProperty("JAVA_HOME");
3484             if (javaHome == null || javaHome.isEmpty()) {
3485                 throw new IOException("The environment variable JAVA_HOME is not correctly set.");
3486             }
3487             if ((!new File(javaHome).getCanonicalFile().exists())
3488                     || (new File(javaHome).getCanonicalFile().isFile())) {
3489                 throw new IOException("The environment variable JAVA_HOME=" + javaHome
3490                         + " doesn't exist or is not a valid directory.");
3491             }
3492 
3493             javadocExe = new File(javaHome + File.separator + "bin", javadocCommand);
3494         }
3495 
3496         if (!javadocExe.getCanonicalFile().exists()
3497                 || !javadocExe.getCanonicalFile().isFile()) {
3498             throw new IOException("The javadoc executable '" + javadocExe
3499                     + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable.");
3500         }
3501 
3502         return javadocExe.getAbsolutePath();
3503     }
3504 
3505     /**
3506      * Set a new value for <code>javadocRuntimeVersion</code>
3507      *
3508      * @param jExecutable not null
3509      * @throws MavenReportException if not found
3510      * @see JavadocUtil#getJavadocVersion(File)
3511      */
3512     private void setFJavadocVersion(File jExecutable) throws MavenReportException {
3513         JavaVersion jVersion;
3514         try {
3515             jVersion = JavadocUtil.getJavadocVersion(jExecutable);
3516         } catch (IOException | CommandLineException | IllegalArgumentException e) {
3517             if (getLog().isWarnEnabled()) {
3518                 getLog().warn("Unable to find the javadoc version: " + e.getMessage());
3519                 getLog().warn("Using the Java version instead of, i.e. " + JAVA_VERSION);
3520             }
3521             jVersion = JAVA_VERSION;
3522         }
3523 
3524         if (javadocVersion != null && !javadocVersion.isEmpty()) {
3525             try {
3526                 javadocRuntimeVersion = JavaVersion.parse(javadocVersion);
3527             } catch (NumberFormatException e) {
3528                 throw new MavenReportException("Unable to parse javadoc version: " + e.getMessage(), e);
3529             }
3530 
3531             if (javadocRuntimeVersion.compareTo(jVersion) != 0 && getLog().isWarnEnabled()) {
3532                 getLog().warn("Are you sure about the <javadocVersion/> parameter? It seems to be " + jVersion);
3533             }
3534         } else {
3535             javadocRuntimeVersion = jVersion;
3536         }
3537     }
3538 
3539     /**
3540      * Is the Javadoc version at least the requested version.
3541      *
3542      * @param requiredVersion the required version, for example 1.5f
3543      * @return <code>true</code> if the javadoc version is equal or greater than the
3544      *         required version
3545      */
3546     private boolean isJavaDocVersionAtLeast(JavaVersion requiredVersion) {
3547         return JAVA_VERSION.compareTo(requiredVersion) >= 0;
3548     }
3549 
3550     /**
3551      * Convenience method to add an argument to the <code>command line</code>
3552      * conditionally based on the given flag.
3553      *
3554      * @param arguments a list of arguments, not null
3555      * @param b         the flag which controls if the argument is added or not.
3556      * @param value     the argument value to be added.
3557      */
3558     private void addArgIf(List<String> arguments, boolean b, String value) {
3559         if (b) {
3560             arguments.add(value);
3561         }
3562     }
3563 
3564     /**
3565      * Convenience method to add an argument to the <code>command line</code>
3566      * regarding the requested Java version.
3567      *
3568      * @param arguments           a list of arguments, not null
3569      * @param b                   the flag which controls if the argument is added or not.
3570      * @param value               the argument value to be added.
3571      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
3572      * @see #addArgIf(List, boolean, String)
3573      * @see #isJavaDocVersionAtLeast(JavaVersion)
3574      */
3575     private void addArgIf(List<String> arguments, boolean b, String value, JavaVersion requiredJavaVersion) {
3576         if (b) {
3577             if (isJavaDocVersionAtLeast(requiredJavaVersion)) {
3578                 addArgIf(arguments, true, value);
3579             } else {
3580                 if (getLog().isWarnEnabled()) {
3581                     getLog().warn(value + " option is not supported on Java version < " + requiredJavaVersion
3582                             + ". Ignore this option.");
3583                 }
3584             }
3585         }
3586     }
3587 
3588     /**
3589      * Convenience method to add an argument to the <code>command line</code>
3590      * if the value is not null or empty.
3591      * <p/>
3592      * Moreover, the value could be comma separated.
3593      *
3594      * @param arguments a list of arguments, not null
3595      * @param key       the argument name.
3596      * @param value     the argument value to be added.
3597      * @see #addArgIfNotEmpty(List, String, String, boolean)
3598      */
3599     private void addArgIfNotEmpty(List<String> arguments, String key, String value) {
3600         addArgIfNotEmpty(arguments, key, value, false);
3601     }
3602 
3603     /**
3604      * Convenience method to add an argument to the <code>command line</code>
3605      * if the value is not null or empty.
3606      * <p/>
3607      * Moreover, the value could be comma separated.
3608      *
3609      * @param arguments           a list of arguments, not null
3610      * @param key                 the argument name.
3611      * @param value               the argument value to be added.
3612      * @param repeatKey           repeat or not the key in the command line
3613      * @param splitValue          if <code>true</code> given value will be tokenized by comma
3614      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
3615      * @see #addArgIfNotEmpty(List, String, String, boolean, boolean)
3616      * @see #isJavaDocVersionAtLeast(JavaVersion)
3617      */
3618     private void addArgIfNotEmpty(
3619             List<String> arguments,
3620             String key,
3621             String value,
3622             boolean repeatKey,
3623             boolean splitValue,
3624             JavaVersion requiredJavaVersion) {
3625         if (value != null && !value.isEmpty()) {
3626             if (isJavaDocVersionAtLeast(requiredJavaVersion)) {
3627                 addArgIfNotEmpty(arguments, key, value, repeatKey, splitValue);
3628             } else {
3629                 if (getLog().isWarnEnabled()) {
3630                     getLog().warn(key + " option is not supported on Java version < " + requiredJavaVersion
3631                             + ". Ignore this option.");
3632                 }
3633             }
3634         }
3635     }
3636 
3637     /**
3638      * Convenience method to add an argument to the <code>command line</code>
3639      * if the value is not null or empty.
3640      * <p/>
3641      * Moreover, the value could be comma separated.
3642      *
3643      * @param arguments  a list of arguments, not null
3644      * @param key        the argument name.
3645      * @param value      the argument value to be added.
3646      * @param repeatKey  repeat or not the key in the command line
3647      * @param splitValue if <code>true</code> given value will be tokenized by comma
3648      */
3649     private void addArgIfNotEmpty(
3650             List<String> arguments, String key, String value, boolean repeatKey, boolean splitValue) {
3651         if (value != null && !value.isEmpty()) {
3652             if (key != null && !key.isEmpty()) {
3653                 arguments.add(key);
3654             }
3655 
3656             if (splitValue) {
3657                 StringTokenizer token = new StringTokenizer(value, ",");
3658                 while (token.hasMoreTokens()) {
3659                     String current = token.nextToken().trim();
3660 
3661                     if (current != null && !current.isEmpty()) {
3662                         arguments.add(current);
3663 
3664                         if (token.hasMoreTokens() && repeatKey) {
3665                             arguments.add(key);
3666                         }
3667                     }
3668                 }
3669             } else {
3670                 arguments.add(value);
3671             }
3672         }
3673     }
3674 
3675     /**
3676      * Convenience method to add an argument to the <code>command line</code>
3677      * if the value is not null or empty.
3678      * <p/>
3679      * Moreover, the value could be comma separated.
3680      *
3681      * @param arguments a list of arguments, not null
3682      * @param key       the argument name.
3683      * @param value     the argument value to be added.
3684      * @param repeatKey repeat or not the key in the command line
3685      */
3686     private void addArgIfNotEmpty(List<String> arguments, String key, String value, boolean repeatKey) {
3687         addArgIfNotEmpty(arguments, key, value, repeatKey, true);
3688     }
3689 
3690     /**
3691      * Convenience method to add an argument to the <code>command line</code>
3692      * regarding the requested Java version.
3693      *
3694      * @param arguments           a list of arguments, not null
3695      * @param key                 the argument name.
3696      * @param value               the argument value to be added.
3697      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
3698      * @see #addArgIfNotEmpty(List, String, String, JavaVersion, boolean)
3699      */
3700     private void addArgIfNotEmpty(List<String> arguments, String key, String value, JavaVersion requiredJavaVersion) {
3701         addArgIfNotEmpty(arguments, key, value, requiredJavaVersion, false);
3702     }
3703 
3704     /**
3705      * Convenience method to add an argument to the <code>command line</code>
3706      * regarding the requested Java version.
3707      *
3708      * @param arguments           a list of arguments, not null
3709      * @param key                 the argument name.
3710      * @param value               the argument value to be added.
3711      * @param requiredJavaVersion the required Java version, for example 1.31f or 1.4f
3712      * @param repeatKey           repeat or not the key in the command line
3713      * @see #addArgIfNotEmpty(List, String, String)
3714      * @see #isJavaDocVersionAtLeast
3715      */
3716     private void addArgIfNotEmpty(
3717             List<String> arguments, String key, String value, JavaVersion requiredJavaVersion, boolean repeatKey) {
3718         if (value != null && !value.isEmpty()) {
3719             if (isJavaDocVersionAtLeast(requiredJavaVersion)) {
3720                 addArgIfNotEmpty(arguments, key, value, repeatKey);
3721             } else {
3722                 if (getLog().isWarnEnabled()) {
3723                     getLog().warn(key + " option is not supported on Java version < " + requiredJavaVersion);
3724                 }
3725             }
3726         }
3727     }
3728 
3729     /**
3730      * Convenience method to process {@code offlineLinks} values as individual <code>-linkoffline</code>
3731      * javadoc options.
3732      * <br/>
3733      * If {@code detectOfflineLinks}, try to add javadoc apidocs according Maven conventions for all modules given
3734      * in the project.
3735      *
3736      * @param arguments a list of arguments, not null
3737      * @throws MavenReportException if any
3738      * @see #offlineLinks
3739      * @see #getModulesLinks()
3740      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#package-list">package-list spec</a>
3741      */
3742     private void addLinkofflineArguments(List<String> arguments, Set<OfflineLink> offlineLinksList)
3743             throws MavenReportException {
3744         for (OfflineLink offlineLink : offlineLinksList) {
3745             String url = offlineLink.getUrl();
3746             if (url == null || url.isEmpty()) {
3747                 continue;
3748             }
3749             url = cleanUrl(url);
3750 
3751             String location = offlineLink.getLocation();
3752             if (location == null || location.isEmpty()) {
3753                 continue;
3754             }
3755             if (isValidJavadocLink(location, false)) {
3756                 addArgIfNotEmpty(
3757                         arguments,
3758                         "-linkoffline",
3759                         JavadocUtil.quotedPathArgument(url) + " " + JavadocUtil.quotedPathArgument(location),
3760                         true);
3761             }
3762         }
3763     }
3764 
3765     private Set<OfflineLink> getOfflineLinks() throws MavenReportException {
3766         Set<OfflineLink> offlineLinksList = collectOfflineLinks();
3767 
3768         offlineLinksList.addAll(getModulesLinks());
3769 
3770         return offlineLinksList;
3771     }
3772 
3773     /**
3774      * Convenience method to process {@link #links} values as individual <code>-link</code> javadoc options.
3775      * If {@code detectLinks}, try to add javadoc apidocs according Maven conventions for all dependencies given
3776      * in the project.
3777      * <br/>
3778      * According to the Javadoc documentation, all defined links should have <code>${link}/package-list</code> fetchable.
3779      * <br/>
3780      * <b>Note</b>: when a link is not fetchable:
3781      * <ul>
3782      * <li>Javadoc 1.4 and less throw an exception</li>
3783      * <li>Javadoc 1.5 and more display a warning</li>
3784      * </ul>
3785      *
3786      * @param arguments a list of arguments, not null
3787      * @throws MavenReportException issue while generating report
3788      * @see #detectLinks
3789      * @see #getDependenciesLinks()
3790      * @see <a href="https://docs.oracle.com/en/java/javase/17/docs/specs/man/javadoc.html#standard-doclet-options">link option</a>
3791      */
3792     private void addLinkArguments(List<String> arguments) throws MavenReportException {
3793         Set<String> links = collectLinks();
3794 
3795         for (String link : links) {
3796             if (link == null || link.isEmpty()) {
3797                 continue;
3798             }
3799 
3800             if ((settings.isOffline() || offline) && !link.startsWith("file:")) {
3801                 continue;
3802             }
3803 
3804             while (link.endsWith("/")) {
3805                 link = link.substring(0, link.lastIndexOf("/"));
3806             }
3807 
3808             addArgIfNotEmpty(arguments, "-link", JavadocUtil.quotedPathArgument(link), true, false);
3809         }
3810     }
3811 
3812     /**
3813      * Copy all resources to the output directory.
3814      *
3815      * @param javadocOutputDirectory not null
3816      * @throws MavenReportException if any
3817      * @see #copyJavadocResources(File)
3818      * @see #copyAdditionalJavadocResources(File)
3819      */
3820     private void copyAllResources(File javadocOutputDirectory) throws MavenReportException {
3821 
3822         // ----------------------------------------------------------------------
3823         // Copy javadoc resources
3824         // ----------------------------------------------------------------------
3825 
3826         if (docfilessubdirs) {
3827             /*
3828              * Workaround since -docfilessubdirs doesn't seem to be used correctly by the javadoc tool
3829              * (see other note about -sourcepath). Take care of the -excludedocfilessubdir option.
3830              */
3831             try {
3832                 copyJavadocResources(javadocOutputDirectory);
3833             } catch (IOException e) {
3834                 throw new MavenReportException("Unable to copy javadoc resources: " + e.getMessage(), e);
3835             }
3836         }
3837 
3838         // ----------------------------------------------------------------------
3839         // Copy additional javadoc resources in artifacts
3840         // ----------------------------------------------------------------------
3841 
3842         copyAdditionalJavadocResources(javadocOutputDirectory);
3843     }
3844 
3845     /**
3846      * Method that copy all <code>doc-files</code> directories from <code>javadocDirectory</code> of
3847      * the current project or of the projects in the reactor to the <code>outputDirectory</code>.
3848      *
3849      * @param anOutputDirectory the output directory
3850      * @throws java.io.IOException if any
3851      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.2.html#docfiles">Reference
3852      *      Guide, Copies new "doc-files" directory for holding images and examples</a>
3853      * @see #docfilessubdirs
3854      */
3855     private void copyJavadocResources(File anOutputDirectory) throws IOException {
3856         if (anOutputDirectory == null || !anOutputDirectory.exists()) {
3857             throw new IOException("The outputDirectory " + anOutputDirectory + " doesn't exists.");
3858         }
3859 
3860         if (includeDependencySources) {
3861             resolveDependencyBundles();
3862             if (isNotEmpty(dependencyJavadocBundles)) {
3863                 for (JavadocBundle bundle : dependencyJavadocBundles) {
3864                     File dir = bundle.getResourcesDirectory();
3865                     JavadocOptions options = bundle.getOptions();
3866                     if (dir != null && dir.isDirectory()) {
3867                         JavadocUtil.copyJavadocResources(
3868                                 anOutputDirectory, dir, options == null ? null : options.getExcludedDocfilesSubdirs());
3869                     }
3870                 }
3871             }
3872         }
3873 
3874         if (getJavadocDirectory() != null) {
3875             JavadocUtil.copyJavadocResources(anOutputDirectory, getJavadocDirectory(), excludedocfilessubdir);
3876         }
3877 
3878         if (isAggregator()) {
3879             for (MavenProject subProject : getAggregatedProjects()) {
3880                 if (subProject != project && getJavadocDirectory() != null) {
3881                     String javadocDirRelative = PathUtils.toRelative(
3882                             project.getBasedir(), getJavadocDirectory().getAbsolutePath());
3883                     File javadocDir = new File(subProject.getBasedir(), javadocDirRelative);
3884                     JavadocUtil.copyJavadocResources(anOutputDirectory, javadocDir, excludedocfilessubdir);
3885                 }
3886             }
3887         }
3888     }
3889 
3890     private synchronized void resolveDependencyBundles() throws IOException {
3891         if (dependencyJavadocBundles == null) {
3892             dependencyJavadocBundles =
3893                     resourceResolver.resolveDependencyJavadocBundles(getDependencySourceResolverConfig());
3894             if (dependencyJavadocBundles == null) {
3895                 dependencyJavadocBundles = new ArrayList<>();
3896             }
3897         }
3898     }
3899 
3900     /**
3901      * Method that copy additional Javadoc resources from given artifacts.
3902      *
3903      * @param anOutputDirectory the output directory
3904      * @throws MavenReportException if any
3905      * @see #resourcesArtifacts
3906      */
3907     private void copyAdditionalJavadocResources(File anOutputDirectory) throws MavenReportException {
3908         Set<ResourcesArtifact> resourcesArtifacts = collectResourcesArtifacts();
3909         if (isEmpty(resourcesArtifacts)) {
3910             return;
3911         }
3912 
3913         UnArchiver unArchiver;
3914         try {
3915             unArchiver = archiverManager.getUnArchiver("jar");
3916         } catch (NoSuchArchiverException e) {
3917             throw new MavenReportException(
3918                     "Unable to extract resources artifact. " + "No archiver for 'jar' available.", e);
3919         }
3920 
3921         for (ResourcesArtifact item : resourcesArtifacts) {
3922             Artifact artifact;
3923             try {
3924                 artifact = createAndResolveArtifact(item);
3925             } catch (ArtifactResolutionException e) {
3926                 throw new MavenReportException("Unable to resolve artifact:" + item, e);
3927             }
3928 
3929             unArchiver.setSourceFile(artifact.getFile());
3930             unArchiver.setDestDirectory(anOutputDirectory);
3931             // remove the META-INF directory from resource artifact
3932             IncludeExcludeFileSelector[] selectors =
3933                     new IncludeExcludeFileSelector[] {new IncludeExcludeFileSelector()};
3934             selectors[0].setExcludes(new String[] {"META-INF/**"});
3935             unArchiver.setFileSelectors(selectors);
3936 
3937             getLog().debug("Extracting contents of resources artifact: " + artifact.getArtifactId());
3938             try {
3939                 unArchiver.extract();
3940             } catch (ArchiverException e) {
3941                 throw new MavenReportException(
3942                         "Extraction of resources failed. Artifact that failed was: " + artifact.getArtifactId(), e);
3943             }
3944         }
3945     }
3946 
3947     /**
3948      * @param sourcePaths could be null
3949      * @return the list of package names for files in the sourcePaths
3950      */
3951     private List<String> getPackageNames(Map<Path, Collection<String>> sourcePaths) {
3952         List<String> returnList = new ArrayList<>();
3953 
3954         if (!(sourcepath == null || sourcepath.isEmpty())) {
3955             return returnList;
3956         }
3957 
3958         for (Entry<Path, Collection<String>> currentPathEntry : sourcePaths.entrySet()) {
3959             for (String currentFile : currentPathEntry.getValue()) {
3960                 /*
3961                  * Remove the miscellaneous files
3962                  * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
3963                  */
3964                 if (currentFile.contains("doc-files")) {
3965                     continue;
3966                 }
3967 
3968                 int lastIndexOfSeparator = currentFile.lastIndexOf("/");
3969                 if (lastIndexOfSeparator != -1) {
3970                     String packagename =
3971                             currentFile.substring(0, lastIndexOfSeparator).replace('/', '.');
3972 
3973                     if (!returnList.contains(packagename)) {
3974                         returnList.add(packagename);
3975                     }
3976                 }
3977             }
3978         }
3979 
3980         return returnList;
3981     }
3982 
3983     /**
3984      * @param javadocModules     not null
3985      * @return a list of exported package names for files in allSourcePaths
3986      * @throws MavenReportException if any
3987      * @see #getFiles
3988      * @see #getSourcePaths()
3989      */
3990     private Collection<String> getPackageNamesRespectingJavaModules(Collection<JavadocModule> javadocModules)
3991             throws MavenReportException {
3992         if (!(sourcepath == null || sourcepath.isEmpty())) {
3993             return Collections.emptyList();
3994         }
3995 
3996         Set<String> returnList = new LinkedHashSet<>();
3997         for (JavadocModule javadocModule : javadocModules) {
3998             Collection<Path> artifactSourcePaths = javadocModule.getSourcePaths();
3999             Set<String> exportedPackages = new HashSet<>();
4000             boolean exportAllPackages;
4001             ResolvePathResult resolvedPath = getResolvePathResult(javadocModule.getArtifactFile());
4002             if (resolvedPath != null && resolvedPath.getModuleNameSource() == ModuleNameSource.MODULEDESCRIPTOR) {
4003                 Set<JavaModuleDescriptor.JavaExports> exports =
4004                         resolvedPath.getModuleDescriptor().exports();
4005                 if (exports.isEmpty()) {
4006                     continue;
4007                 }
4008                 for (JavaModuleDescriptor.JavaExports export : exports) {
4009                     exportedPackages.add(export.source());
4010                 }
4011                 exportAllPackages = false;
4012             } else {
4013                 exportAllPackages = true;
4014             }
4015 
4016             for (Map.Entry<Path, Collection<String>> currentPathEntry :
4017                     getFiles(artifactSourcePaths).entrySet()) {
4018                 for (String currentFile : currentPathEntry.getValue()) {
4019                     /*
4020                      * Remove the miscellaneous files
4021                      * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
4022                      */
4023                     if (currentFile.contains("doc-files")) {
4024                         continue;
4025                     }
4026 
4027                     int lastIndexOfSeparator = currentFile.lastIndexOf('/');
4028                     if (lastIndexOfSeparator != -1) {
4029                         String packagename =
4030                                 currentFile.substring(0, lastIndexOfSeparator).replace('/', '.');
4031 
4032                         if (exportAllPackages || exportedPackages.contains(packagename)) {
4033                             returnList.add(packagename);
4034                         }
4035                     }
4036                 }
4037             }
4038         }
4039 
4040         return returnList;
4041     }
4042 
4043     /**
4044      * @param sourcePaths could be null
4045      * @return a list files with unnamed package names for files in the sourcePaths
4046      */
4047     private List<String> getFilesWithUnnamedPackages(Map<Path, Collection<String>> sourcePaths) {
4048         List<String> returnList = new ArrayList<>();
4049 
4050         if (!(sourcepath == null || sourcepath.isEmpty())) {
4051             return returnList;
4052         }
4053 
4054         for (Entry<Path, Collection<String>> currentPathEntry : sourcePaths.entrySet()) {
4055             Path currentSourcePath = currentPathEntry.getKey();
4056 
4057             for (String currentFile : currentPathEntry.getValue()) {
4058                 /*
4059                  * Remove the miscellaneous files
4060                  * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
4061                  */
4062                 if (currentFile.contains("doc-files")) {
4063                     continue;
4064                 }
4065 
4066                 if (currentFile.indexOf('/') == -1) {
4067                     returnList.add(currentSourcePath
4068                             .resolve(currentFile)
4069                             .toAbsolutePath()
4070                             .toString());
4071                 }
4072             }
4073         }
4074 
4075         return returnList;
4076     }
4077 
4078     /**
4079      * Either return only the module descriptor or all sourcefiles per sourcepath
4080      * @param sourcePaths could be null
4081      * @return a list of files
4082      */
4083     private List<String> getSpecialFiles(Map<Path, Collection<String>> sourcePaths) {
4084         if (!(sourcepath == null || sourcepath.isEmpty())) {
4085             return new ArrayList<>();
4086         }
4087 
4088         boolean containsModuleDescriptor = false;
4089         for (Collection<String> sourcepathFiles : sourcePaths.values()) {
4090             containsModuleDescriptor = sourcepathFiles.contains("module-info.java");
4091             if (containsModuleDescriptor) {
4092                 break;
4093             }
4094         }
4095 
4096         if (containsModuleDescriptor) {
4097             return getModuleSourcePathFiles(sourcePaths);
4098         } else {
4099             return getFilesWithUnnamedPackages(sourcePaths);
4100         }
4101     }
4102 
4103     private List<String> getModuleSourcePathFiles(Map<Path, Collection<String>> sourcePaths) {
4104         List<String> returnList = new ArrayList<>();
4105 
4106         for (Entry<Path, Collection<String>> currentPathEntry : sourcePaths.entrySet()) {
4107             Path currentSourcePath = currentPathEntry.getKey();
4108             if (currentPathEntry.getValue().contains("module-info.java")) {
4109                 returnList.add(currentSourcePath
4110                         .resolve("module-info.java")
4111                         .toAbsolutePath()
4112                         .toString());
4113             } else {
4114                 for (String currentFile : currentPathEntry.getValue()) {
4115                     /*
4116                      * Remove the miscellaneous files
4117                      * https://docs.oracle.com/javase/1.4.2/docs/tooldocs/solaris/javadoc.html#unprocessed
4118                      */
4119                     if (currentFile.contains("doc-files")) {
4120                         continue;
4121                     }
4122 
4123                     returnList.add(currentSourcePath
4124                             .resolve(currentFile)
4125                             .toAbsolutePath()
4126                             .toString());
4127                 }
4128             }
4129         }
4130         return returnList;
4131     }
4132 
4133     /**
4134      * Generate an <code>options</code> file for all options and arguments and add the <code>@options</code> in the
4135      * command line.
4136      *
4137      * @param cmd                    not null
4138      * @param arguments              not null
4139      * @param javadocOutputDirectory not null
4140      * @throws MavenReportException if any
4141      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#argumentfiles">
4142      *      Reference Guide, Command line argument files</a>
4143      * @see #OPTIONS_FILE_NAME
4144      */
4145     private void addCommandLineOptions(Commandline cmd, List<String> arguments, File javadocOutputDirectory)
4146             throws MavenReportException {
4147         File optionsFile = new File(javadocOutputDirectory, OPTIONS_FILE_NAME);
4148 
4149         StringBuilder options = new StringBuilder();
4150         options.append(StringUtils.join(arguments.iterator(), SystemUtils.LINE_SEPARATOR));
4151 
4152         Charset outputFileEncoding;
4153         if (JAVA_VERSION.isAtLeast("9") && JAVA_VERSION.isBefore("12")) {
4154             outputFileEncoding = StandardCharsets.UTF_8;
4155         } else {
4156             outputFileEncoding = Charset.defaultCharset();
4157         }
4158         try {
4159             Files.write(optionsFile.toPath(), Collections.singleton(options), outputFileEncoding);
4160         } catch (IOException e) {
4161             throw new MavenReportException(
4162                     "Unable to write '" + optionsFile.getName() + "' temporary file for command execution", e);
4163         }
4164 
4165         cmd.createArg().setValue("@" + OPTIONS_FILE_NAME);
4166     }
4167 
4168     /**
4169      * Generate a file called <code>argfile</code> (or <code>files</code>, depending on the JDK) to hold files and add
4170      * the <code>@argfile</code> (or <code>@file</code>, depending on the JDK) in the command line.
4171      *
4172      * @param cmd                    not null
4173      * @param javadocOutputDirectory not null
4174      * @param files                  not null
4175      * @throws MavenReportException if any
4176      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#argumentfiles">
4177      *      Reference Guide, Command line argument files
4178      *      </a>
4179      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#runningjavadoc">
4180      *      What s New in Javadoc 1.4
4181      *      </a>
4182      * @see #isJavaDocVersionAtLeast(JavaVersion)
4183      * @see #ARGFILE_FILE_NAME
4184      * @see #FILES_FILE_NAME
4185      */
4186     private void addCommandLineArgFile(Commandline cmd, File javadocOutputDirectory, List<String> files)
4187             throws MavenReportException {
4188         File argfileFile;
4189         argfileFile = new File(javadocOutputDirectory, ARGFILE_FILE_NAME);
4190         cmd.createArg().setValue("@" + ARGFILE_FILE_NAME);
4191 
4192         List<String> quotedFiles = new ArrayList<>(files.size());
4193         for (String file : files) {
4194             quotedFiles.add(JavadocUtil.quotedPathArgument(file));
4195         }
4196 
4197         Charset cs;
4198         if (JavaVersion.JAVA_SPECIFICATION_VERSION.isAtLeast("9")
4199                 && JavaVersion.JAVA_SPECIFICATION_VERSION.isBefore("12")) {
4200             cs = StandardCharsets.UTF_8;
4201         } else {
4202             cs = Charset.defaultCharset();
4203         }
4204 
4205         try {
4206             Files.write(argfileFile.toPath(), quotedFiles, cs);
4207         } catch (IOException e) {
4208             throw new MavenReportException(
4209                     "Unable to write '" + argfileFile.getName() + "' temporary file for command execution", e);
4210         }
4211     }
4212 
4213     /**
4214      * Generate a file called <code>packages</code> to hold all package names and add the <code>@packages</code> in
4215      * the command line.
4216      *
4217      * @param cmd                    not null
4218      * @param javadocOutputDirectory not null
4219      * @param packageNames           not null
4220      * @throws MavenReportException if any
4221      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#argumentfiles">
4222      *      Reference Guide, Command line argument files</a>
4223      * @see #PACKAGES_FILE_NAME
4224      */
4225     private void addCommandLinePackages(Commandline cmd, File javadocOutputDirectory, Collection<String> packageNames)
4226             throws MavenReportException {
4227         File packagesFile = new File(javadocOutputDirectory, PACKAGES_FILE_NAME);
4228 
4229         try {
4230             FileUtils.fileWrite(
4231                     packagesFile.getAbsolutePath(),
4232                     null /* platform encoding */,
4233                     StringUtils.join(packageNames.iterator(), SystemUtils.LINE_SEPARATOR));
4234         } catch (IOException e) {
4235             throw new MavenReportException(
4236                     "Unable to write '" + packagesFile.getName() + "' temporary file for command execution", e);
4237         }
4238 
4239         cmd.createArg().setValue("@" + PACKAGES_FILE_NAME);
4240     }
4241 
4242     /**
4243      * Checks for the validity of the Javadoc options used by the user.
4244      *
4245      * @throws MavenReportException if error
4246      */
4247     private void validateJavadocOptions() throws MavenReportException {
4248         // encoding
4249         if (StringUtils.isNotEmpty(getEncoding()) && !JavadocUtil.validateEncoding(getEncoding())) {
4250             throw new MavenReportException("Unsupported option <encoding/> '" + getEncoding() + "'");
4251         }
4252 
4253         // locale
4254         if (StringUtils.isNotEmpty(this.locale)) {
4255             this.locale = siteTool.getSiteLocales(locale).get(0).toString();
4256         }
4257     }
4258 
4259     /**
4260      * Checks for the validity of the Standard Doclet options.
4261      * <br/>
4262      * For example, throw an exception if &lt;nohelp/&gt; and &lt;helpfile/&gt; options are used together.
4263      *
4264      * @throws MavenReportException if error or conflict found
4265      */
4266     private void validateStandardDocletOptions() throws MavenReportException {
4267         // docencoding
4268         if (StringUtils.isNotEmpty(getDocencoding()) && !JavadocUtil.validateEncoding(getDocencoding())) {
4269             throw new MavenReportException("Unsupported option <docencoding/> '" + getDocencoding() + "'");
4270         }
4271 
4272         // charset
4273         if (StringUtils.isNotEmpty(getCharset()) && !JavadocUtil.validateEncoding(getCharset())) {
4274             throw new MavenReportException("Unsupported option <charset/> '" + getCharset() + "'");
4275         }
4276 
4277         // helpfile
4278         if ((helpfile != null && !helpfile.isEmpty()) && nohelp) {
4279             throw new MavenReportException("Option <nohelp/> conflicts with <helpfile/>");
4280         }
4281 
4282         // overview
4283         if (getOverview() != null && getOverview().exists() && nooverview) {
4284             throw new MavenReportException("Option <nooverview/> conflicts with <overview/>");
4285         }
4286 
4287         // index
4288         if (splitindex && noindex) {
4289             throw new MavenReportException("Option <noindex/> conflicts with <splitindex/>");
4290         }
4291 
4292         // stylesheet
4293         if ((stylesheet != null && !stylesheet.isEmpty())
4294                 && !(stylesheet.equalsIgnoreCase("maven") || stylesheet.equalsIgnoreCase("java"))) {
4295             throw new MavenReportException("Option <stylesheet/> supports only \"maven\" or \"java\" value.");
4296         }
4297     }
4298 
4299     /**
4300      * Add Standard Javadoc Options.
4301      * <br/>
4302      * The <a href="package-summary.html#Standard_Javadoc_Options">package documentation</a> details the
4303      * Standard Javadoc Options wrapped by this Plugin.
4304      *
4305      * @param javadocOutputDirectory not null
4306      * @param arguments              not null
4307      * @param allSourcePaths         not null
4308      * @throws MavenReportException if any
4309      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadocoptions">https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#javadocoptions</a>
4310      */
4311     private void addJavadocOptions(
4312             File javadocOutputDirectory,
4313             List<String> arguments,
4314             Collection<JavadocModule> allSourcePaths,
4315             Set<OfflineLink> offlineLinks)
4316             throws MavenReportException {
4317         Collection<Path> sourcePaths = allSourcePaths.stream()
4318                 .flatMap(e -> e.getSourcePaths().stream())
4319                 .collect(Collectors.toList());
4320 
4321         validateJavadocOptions();
4322 
4323         // see com.sun.tools.javadoc.Start#parseAndExecute(String argv[])
4324         addArgIfNotEmpty(arguments, "-locale", JavadocUtil.quotedArgument(this.locale));
4325 
4326         // all options in alphabetical order
4327 
4328         addArgIfNotEmpty(arguments, "-bootclasspath", JavadocUtil.quotedPathArgument(getBootclassPath()));
4329 
4330         if (breakiterator) {
4331             addArgIf(arguments, breakiterator, "-breakiterator");
4332         }
4333         List<MavenProject> aggregatedProjects = reactorProjects; // getAggregatedProjects();
4334         Map<String, MavenProject> reactorKeys = new HashMap<>(aggregatedProjects.size());
4335         for (MavenProject reactorProject : aggregatedProjects) {
4336             reactorKeys.put(
4337                     ArtifactUtils.key(
4338                             reactorProject.getGroupId(), reactorProject.getArtifactId(), reactorProject.getVersion()),
4339                     reactorProject);
4340         }
4341 
4342         Map<String, JavaModuleDescriptor> allModuleDescriptors = new HashMap<>();
4343 
4344         // do not support the module path in legacy mode
4345         boolean supportModulePath = !legacyMode;
4346 
4347         if (supportModulePath) {
4348             supportModulePath &= javadocRuntimeVersion.isAtLeast("9");
4349             if (release != null) {
4350                 supportModulePath &= JavaVersion.parse(release).isAtLeast("9");
4351             } else if (source != null) {
4352                 supportModulePath &= JavaVersion.parse(source).isAtLeast("9");
4353             }
4354         }
4355 
4356         if (supportModulePath) {
4357             for (JavadocModule entry : allSourcePaths) {
4358                 if (entry.getModuleNameSource() == null || entry.getModuleNameSource() == ModuleNameSource.FILENAME) {
4359                     Path moduleDescriptor = findMainDescriptor(entry.getSourcePaths());
4360 
4361                     if (moduleDescriptor != null) {
4362                         try {
4363                             allModuleDescriptors.put(
4364                                     entry.getGav(),
4365                                     locationManager
4366                                             .parseModuleDescriptor(moduleDescriptor)
4367                                             .getModuleDescriptor());
4368                         } catch (IOException e) {
4369                             throw new MavenReportException(e.getMessage(), e);
4370                         }
4371                     }
4372                 } else {
4373                     allModuleDescriptors.put(entry.getGav(), entry.getModuleDescriptor());
4374                 }
4375             }
4376         }
4377 
4378         Collection<String> additionalModules = new ArrayList<>();
4379 
4380         ResolvePathResult mainResolvePathResult = null;
4381 
4382         Map<String, Collection<Path>> patchModules = new HashMap<>();
4383 
4384         Path moduleSourceDir = null;
4385         if (supportModulePath && !allModuleDescriptors.isEmpty()) {
4386             Collection<String> unnamedProjects = new ArrayList<>();
4387             for (JavadocModule javadocModule : allSourcePaths) {
4388                 MavenProject aggregatedProject = reactorKeys.get(javadocModule.getGav());
4389                 if (aggregatedProject != null && !"pom".equals(aggregatedProject.getPackaging())) {
4390                     ResolvePathResult result = null;
4391 
4392                     // Prefer jar over outputDirectory, since it may contain an automatic module name
4393                     File artifactFile = getClassesFile(aggregatedProject);
4394                     if (artifactFile != null) {
4395                         ResolvePathRequest<File> request = ResolvePathRequest.ofFile(artifactFile);
4396                         try {
4397                             result = locationManager.resolvePath(request);
4398                         } catch (RuntimeException e) {
4399                             // most likely an invalid module name based on filename
4400                             if (!"java.lang.module.FindException"
4401                                     .equals(e.getClass().getName())) {
4402                                 throw e;
4403                             }
4404                         } catch (IOException e) {
4405                             throw new MavenReportException(e.getMessage(), e);
4406                         }
4407                     } else {
4408                         Path moduleDescriptor = findMainDescriptor(javadocModule.getSourcePaths());
4409 
4410                         if (moduleDescriptor != null) {
4411                             try {
4412                                 result = locationManager.parseModuleDescriptor(moduleDescriptor);
4413                             } catch (IOException e) {
4414                                 throw new MavenReportException(e.getMessage(), e);
4415                             }
4416                         }
4417                     }
4418 
4419                     if (result != null && result.getModuleDescriptor() != null) {
4420                         moduleSourceDir = javadocOutputDirectory.toPath().resolve("src");
4421                         try {
4422                             moduleSourceDir = Files.createDirectories(moduleSourceDir);
4423 
4424                             additionalModules.add(result.getModuleDescriptor().name());
4425 
4426                             patchModules.put(result.getModuleDescriptor().name(), javadocModule.getSourcePaths());
4427 
4428                             Path modulePath = moduleSourceDir.resolve(
4429                                     result.getModuleDescriptor().name());
4430                             if (!Files.isDirectory(modulePath)) {
4431                                 Files.createDirectory(modulePath);
4432                             }
4433                         } catch (IOException e) {
4434                             throw new MavenReportException(e.getMessage(), e);
4435                         }
4436                     } else {
4437                         unnamedProjects.add(javadocModule.getGav());
4438                     }
4439 
4440                     if (aggregatedProject.equals(getProject())) {
4441                         mainResolvePathResult = result;
4442                     }
4443                 } else {
4444                     // todo
4445                     getLog().error("no reactor project: " + javadocModule.getGav());
4446                 }
4447             }
4448 
4449             if (!unnamedProjects.isEmpty()) {
4450                 getLog().error("Creating an aggregated report for both named and unnamed modules is not possible.");
4451                 getLog().error("Ensure that every module has a module descriptor or is a jar with a MANIFEST.MF "
4452                         + "containing an Automatic-Module-Name.");
4453                 getLog().error("Fix the following projects:");
4454                 for (String unnamedProject : unnamedProjects) {
4455                     getLog().error(" - " + unnamedProject);
4456                 }
4457                 throw new MavenReportException("Aggregator report contains named and unnamed modules");
4458             }
4459 
4460             if (mainResolvePathResult != null
4461                     && ModuleNameSource.MANIFEST.equals(mainResolvePathResult.getModuleNameSource())) {
4462                 arguments.add("--add-modules");
4463                 arguments.add("ALL-MODULE-PATH");
4464             }
4465         }
4466 
4467         // MJAVADOC-506
4468         boolean moduleDescriptorSource = false;
4469         for (Path sourcepath : sourcePaths) {
4470             if (Files.isRegularFile(sourcepath.resolve("module-info.java"))) {
4471                 moduleDescriptorSource = true;
4472                 break;
4473             }
4474         }
4475 
4476         final ModuleNameSource mainModuleNameSource;
4477         if (mainResolvePathResult != null) {
4478             mainModuleNameSource = mainResolvePathResult.getModuleNameSource();
4479         } else {
4480             mainModuleNameSource = null;
4481         }
4482 
4483         if (supportModulePath
4484                 && (isAggregator()
4485                         || ModuleNameSource.MODULEDESCRIPTOR.equals(mainModuleNameSource)
4486                         || ModuleNameSource.MANIFEST.equals(mainModuleNameSource))) {
4487             List<File> pathElements = new ArrayList<>(getPathElements());
4488             File artifactFile = getClassesFile(project);
4489             if (artifactFile != null) {
4490                 pathElements.add(0, artifactFile);
4491             }
4492 
4493             ResolvePathsRequest<File> request = ResolvePathsRequest.ofFiles(pathElements);
4494 
4495             String mainModuleName = null;
4496             if (mainResolvePathResult != null) {
4497                 request.setModuleDescriptor(mainResolvePathResult.getModuleDescriptor());
4498                 mainModuleName = mainResolvePathResult.getModuleDescriptor().name();
4499             }
4500 
4501             request.setAdditionalModules(additionalModules);
4502             request.setIncludeStatic(isAggregator());
4503 
4504             try {
4505                 ResolvePathsResult<File> result = locationManager.resolvePaths(request);
4506 
4507                 Set<File> modulePathElements =
4508                         new HashSet<>(result.getModulepathElements().keySet());
4509 
4510                 Collection<File> classPathElements =
4511                         new ArrayList<>(result.getClasspathElements().size());
4512 
4513                 for (File file : result.getClasspathElements()) {
4514                     if (file.isDirectory() && new File(file, "module-info.class").exists()) {
4515                         modulePathElements.add(file);
4516                     } else if (ModuleNameSource.MANIFEST.equals(mainModuleNameSource)) {
4517                         ModuleNameSource depModuleNameSource = locationManager
4518                                 .resolvePath(ResolvePathRequest.ofFile(file))
4519                                 .getModuleNameSource();
4520                         if (ModuleNameSource.MODULEDESCRIPTOR.equals(depModuleNameSource)) {
4521                             modulePathElements.add(file);
4522                         } else {
4523                             patchModules.get(mainModuleName).add(file.toPath());
4524                         }
4525                     } else {
4526                         classPathElements.add(file);
4527                     }
4528                 }
4529 
4530                 /* MJAVADOC-620: also add all JARs where module-name-guessing leads to a FindException: */
4531                 for (Entry<File, Exception> pathExceptionEntry :
4532                         result.getPathExceptions().entrySet()) {
4533                     Exception exception = pathExceptionEntry.getValue();
4534                     // For Java < 9 compatibility, reference FindException by name:
4535                     if ("java.lang.module.FindException"
4536                             .equals(exception.getClass().getName())) {
4537                         File jarPath = pathExceptionEntry.getKey();
4538                         classPathElements.add(jarPath);
4539                     }
4540                 }
4541 
4542                 String classpath = StringUtils.join(classPathElements.iterator(), File.pathSeparator);
4543                 addArgIfNotEmpty(arguments, "--class-path", JavadocUtil.quotedPathArgument(classpath), false, false);
4544 
4545                 String modulepath = StringUtils.join(modulePathElements.iterator(), File.pathSeparator);
4546                 addArgIfNotEmpty(arguments, "--module-path", JavadocUtil.quotedPathArgument(modulepath), false, false);
4547             } catch (IOException e) {
4548                 throw new MavenReportException(e.getMessage(), e);
4549             }
4550         } else if (supportModulePath && moduleDescriptorSource && !isTest()) {
4551             String modulepath = StringUtils.join(getPathElements().iterator(), File.pathSeparator);
4552             addArgIfNotEmpty(arguments, "--module-path", JavadocUtil.quotedPathArgument(modulepath), false, false);
4553         } else {
4554             String classpath = StringUtils.join(getPathElements().iterator(), File.pathSeparator);
4555             addArgIfNotEmpty(arguments, "-classpath", JavadocUtil.quotedPathArgument(classpath), false, false);
4556         }
4557 
4558         for (Entry<String, Collection<Path>> entry : patchModules.entrySet()) {
4559             if (!entry.getValue().isEmpty()) {
4560                 addArgIfNotEmpty(
4561                         arguments,
4562                         "--patch-module",
4563                         entry.getKey() + '=' + JavadocUtil.quotedPathArgument(getSourcePath(entry.getValue())),
4564                         false,
4565                         false);
4566             }
4567         }
4568 
4569         if (doclet != null && !doclet.isEmpty()) {
4570             addArgIfNotEmpty(arguments, "-doclet", JavadocUtil.quotedArgument(doclet));
4571             addArgIfNotEmpty(arguments, "-docletpath", JavadocUtil.quotedPathArgument(getDocletPath()));
4572         }
4573 
4574         if (encoding == null || encoding.isEmpty()) {
4575             getLog().warn("Source files encoding has not been set, using platform encoding "
4576                     + Charset.defaultCharset().name() + ", i.e. build is platform dependent!");
4577         }
4578         addArgIfNotEmpty(arguments, "-encoding", JavadocUtil.quotedArgument(getEncoding()));
4579 
4580         addArgIfNotEmpty(
4581                 arguments, "-extdirs", JavadocUtil.quotedPathArgument(JavadocUtil.unifyPathSeparator(extdirs)));
4582 
4583         if ((getOverview() != null) && (getOverview().exists())) {
4584             addArgIfNotEmpty(
4585                     arguments,
4586                     "-overview",
4587                     JavadocUtil.quotedPathArgument(getOverview().getAbsolutePath()));
4588         }
4589 
4590         arguments.add(getAccessLevel());
4591 
4592         addArgIf(arguments, quiet, "-quiet");
4593 
4594         if (javadocRuntimeVersion.isAtLeast("9") && release != null) {
4595             arguments.add("--release");
4596             arguments.add(release);
4597         } else {
4598             addArgIfNotEmpty(arguments, "-source", JavadocUtil.quotedArgument(source));
4599         }
4600 
4601         if ((sourcepath == null || sourcepath.isEmpty()) && (subpackages != null && !subpackages.isEmpty())) {
4602             sourcepath = StringUtils.join(sourcePaths.iterator(), File.pathSeparator);
4603         }
4604 
4605         if (moduleSourceDir == null) {
4606             if (!disableSourcepathUsage
4607                     && (!legacyMode || (excludePackageNames != null && !excludePackageNames.isEmpty()))) {
4608                 addArgIfNotEmpty(
4609                         arguments,
4610                         "-sourcepath",
4611                         JavadocUtil.quotedPathArgument(getSourcePath(sourcePaths)),
4612                         false,
4613                         false);
4614             }
4615 
4616         } else if (mainResolvePathResult == null
4617                 || ModuleNameSource.MODULEDESCRIPTOR.equals(mainResolvePathResult.getModuleNameSource())) {
4618             addArgIfNotEmpty(
4619                     arguments, "--module-source-path", JavadocUtil.quotedPathArgument(moduleSourceDir.toString()));
4620         }
4621 
4622         addArgIfNotEmpty(arguments, "-subpackages", subpackages);
4623 
4624         // [MJAVADOC-497] must be after sourcepath is recalculated, since getExcludedPackages() depends on it
4625         addArgIfNotEmpty(arguments, "-exclude", getExcludedPackages(sourcePaths));
4626 
4627         addArgIf(arguments, verbose, "-verbose");
4628 
4629         if (additionalOptions != null) {
4630             for (String additionalOption : additionalOptions) {
4631                 arguments.add(additionalOption.replaceAll("(?<!\\\\)\\\\(?!\\\\|:)", "\\\\"));
4632             }
4633         }
4634     }
4635 
4636     private ResolvePathResult getResolvePathResult(File artifactFile) {
4637         if (artifactFile == null) {
4638             return null;
4639         }
4640 
4641         ResolvePathResult resolvePathResult = null;
4642         ResolvePathRequest<File> resolvePathRequest = ResolvePathRequest.ofFile(artifactFile);
4643         try {
4644             resolvePathResult = locationManager.resolvePath(resolvePathRequest);
4645 
4646             // happens when artifactFile is a directory without module descriptor
4647             if (resolvePathResult.getModuleDescriptor() == null) {
4648                 return null;
4649             }
4650         } catch (IOException | RuntimeException /* e.g java.lang.module.FindException */ e) {
4651             if (getLog().isDebugEnabled()) {
4652                 Throwable cause = e;
4653                 while (cause.getCause() != null) {
4654                     cause = cause.getCause();
4655                 }
4656 
4657                 getLog().debug("resolve path for: " + artifactFile + " cause error: " + cause);
4658             }
4659         }
4660         return resolvePathResult;
4661     }
4662 
4663     private Path findMainDescriptor(Collection<Path> roots) throws MavenReportException {
4664         for (Map.Entry<Path, Collection<String>> entry : getFiles(roots).entrySet()) {
4665             if (entry.getValue().contains("module-info.java")) {
4666                 return entry.getKey().resolve("module-info.java");
4667             }
4668         }
4669         return null;
4670     }
4671 
4672     /**
4673      * Add Standard Doclet Options.
4674      * <br/>
4675      * The <a href="package-summary.html#Standard_Doclet_Options">package documentation</a> details the
4676      * Standard Doclet Options wrapped by this Plugin.
4677      *
4678      * @param javadocOutputDirectory not null
4679      * @param arguments              not null
4680      * @throws MavenReportException if any
4681      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard">
4682      *      https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#standard</a>
4683      */
4684     private void addStandardDocletOptions(
4685             File javadocOutputDirectory, List<String> arguments, Set<OfflineLink> offlineLinks)
4686             throws MavenReportException {
4687         validateStandardDocletOptions();
4688 
4689         // all options in alphabetical order
4690 
4691         addArgIf(arguments, author, "-author");
4692 
4693         addArgIfNotEmpty(arguments, "-bottom", JavadocUtil.quotedArgument(getBottomText()), false, false);
4694 
4695         addArgIf(arguments, breakiterator, "-breakiterator");
4696 
4697         addArgIfNotEmpty(arguments, "-charset", JavadocUtil.quotedArgument(getCharset()));
4698 
4699         addArgIfNotEmpty(arguments, "-d", JavadocUtil.quotedPathArgument(javadocOutputDirectory.toString()));
4700 
4701         addArgIfNotEmpty(arguments, "-docencoding", JavadocUtil.quotedArgument(getDocencoding()));
4702 
4703         addArgIf(arguments, docfilessubdirs, "-docfilessubdirs");
4704 
4705         addArgIf(arguments, (doclint != null && !doclint.isEmpty()), "-Xdoclint:" + getDoclint(), SINCE_JAVADOC_1_8);
4706 
4707         addArgIfNotEmpty(arguments, "-doctitle", JavadocUtil.quotedArgument(getDoctitle()), false, false);
4708 
4709         if (docfilessubdirs) {
4710             addArgIfNotEmpty(
4711                     arguments, "-excludedocfilessubdir", JavadocUtil.quotedPathArgument(excludedocfilessubdir));
4712         }
4713 
4714         addArgIfNotEmpty(arguments, "-footer", JavadocUtil.quotedArgument(footer), false, false);
4715 
4716         addGroups(arguments);
4717 
4718         addArgIfNotEmpty(arguments, "-header", JavadocUtil.quotedArgument(header), false, false);
4719 
4720         getHelpFile(javadocOutputDirectory)
4721                 .ifPresent(file -> addArgIfNotEmpty(
4722                         arguments, "-helpfile", JavadocUtil.quotedPathArgument(file.getAbsolutePath())));
4723 
4724         addArgIf(arguments, keywords, "-keywords");
4725 
4726         addLinkArguments(arguments);
4727 
4728         addLinkofflineArguments(arguments, offlineLinks);
4729 
4730         addArgIf(arguments, linksource, "-linksource");
4731 
4732         if (sourcetab > 0) {
4733             addArgIfNotEmpty(arguments, "-sourcetab", String.valueOf(sourcetab));
4734         }
4735 
4736         addArgIf(arguments, nocomment, "-nocomment");
4737 
4738         addArgIf(arguments, nodeprecated, "-nodeprecated");
4739 
4740         addArgIf(arguments, nodeprecatedlist, "-nodeprecatedlist");
4741 
4742         addArgIf(arguments, nohelp, "-nohelp");
4743 
4744         addArgIf(arguments, noindex, "-noindex");
4745 
4746         addArgIf(arguments, nonavbar, "-nonavbar");
4747 
4748         addArgIf(arguments, nooverview, "-nooverview");
4749 
4750         addArgIfNotEmpty(arguments, "-noqualifier", JavadocUtil.quotedArgument(noqualifier));
4751 
4752         addArgIf(arguments, nosince, "-nosince");
4753 
4754         if (!notimestamp
4755                 && MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).isPresent()) {
4756             // Override the notimestamp option if a Reproducible Build is requested.
4757             notimestamp = true;
4758         }
4759 
4760         addArgIf(arguments, notimestamp, "-notimestamp");
4761 
4762         addArgIf(arguments, notree, "-notree");
4763 
4764         addArgIfNotEmpty(arguments, "-packagesheader", JavadocUtil.quotedArgument(packagesheader));
4765 
4766         addArgIf(arguments, quiet, "-quiet");
4767 
4768         addArgIf(arguments, serialwarn, "-serialwarn");
4769 
4770         addArgIf(arguments, splitindex, "-splitindex");
4771 
4772         getStylesheetFile(javadocOutputDirectory)
4773                 .ifPresent(file -> addArgIfNotEmpty(
4774                         arguments, "-stylesheetfile", JavadocUtil.quotedPathArgument(file.getAbsolutePath())));
4775 
4776         addAddStyleSheets(arguments);
4777 
4778         addArgIfNotEmpty(arguments, "-subpackages", subpackages);
4779 
4780         addArgIfNotEmpty(arguments, "-taglet", JavadocUtil.quotedArgument(taglet));
4781         addTaglets(arguments);
4782         addTagletsFromTagletArtifacts(arguments);
4783         addArgIfNotEmpty(arguments, "-tagletpath", JavadocUtil.quotedPathArgument(getTagletPath()));
4784 
4785         addTags(arguments);
4786 
4787         addArgIfNotEmpty(arguments, "-top", JavadocUtil.quotedArgument(top), false, false);
4788 
4789         addArgIf(arguments, use, "-use");
4790 
4791         addArgIf(arguments, version, "-version");
4792 
4793         addArgIfNotEmpty(arguments, "-windowtitle", JavadocUtil.quotedArgument(getWindowtitle()), false, false);
4794     }
4795 
4796     /**
4797      * Add <code>groups</code> parameter to arguments.
4798      *
4799      * @param arguments not null
4800      * @throws MavenReportException
4801      */
4802     private void addGroups(List<String> arguments) throws MavenReportException {
4803         Set<Group> groups = collectGroups();
4804         if (isEmpty(groups)) {
4805             return;
4806         }
4807 
4808         for (Group group : groups) {
4809             if (group == null || StringUtils.isEmpty(group.getTitle()) || StringUtils.isEmpty(group.getPackages())) {
4810                 if (getLog().isWarnEnabled()) {
4811                     getLog().warn("A group option is empty. Ignore this option.");
4812                 }
4813             } else {
4814                 String groupTitle = group.getTitle().replace(",", "&#44;");
4815                 addArgIfNotEmpty(
4816                         arguments,
4817                         "-group",
4818                         JavadocUtil.quotedArgument(groupTitle) + " " + JavadocUtil.quotedArgument(group.getPackages()),
4819                         true);
4820             }
4821         }
4822     }
4823 
4824     /**
4825      * Add <code>tags</code> parameter to arguments.
4826      *
4827      * @param arguments not null
4828      * @throws MavenReportException
4829      */
4830     private void addTags(List<String> arguments) throws MavenReportException {
4831         final String lineSeparator;
4832         if (javadocRuntimeVersion.isBefore("9")) {
4833             lineSeparator = " ";
4834         } else {
4835             lineSeparator = " \\\\" + SystemUtils.LINE_SEPARATOR;
4836         }
4837 
4838         for (Tag tag : collectTags()) {
4839             if (StringUtils.isEmpty(tag.getName())) {
4840                 if (getLog().isWarnEnabled()) {
4841                     getLog().warn("A tag name is empty. Ignore this option.");
4842                 }
4843             } else {
4844                 String value = "\"" + tag.getName();
4845                 if (StringUtils.isNotEmpty(tag.getPlacement())) {
4846                     value += ":" + tag.getPlacement().replaceAll("\\R", lineSeparator);
4847                     if (StringUtils.isNotEmpty(tag.getHead())) {
4848                         value += ":" + tag.getHead().replaceAll("\\R", lineSeparator);
4849                     }
4850                 }
4851                 value += "\"";
4852                 addArgIfNotEmpty(arguments, "-tag", value);
4853             }
4854         }
4855     }
4856 
4857     /**
4858      * Add <code>taglets</code> parameter to arguments.
4859      *
4860      * @param arguments not null
4861      */
4862     private void addTaglets(List<String> arguments) {
4863         if (taglets == null) {
4864             return;
4865         }
4866 
4867         for (Taglet taglet1 : taglets) {
4868             if ((taglet1 == null) || (StringUtils.isEmpty(taglet1.getTagletClass()))) {
4869                 if (getLog().isWarnEnabled()) {
4870                     getLog().warn("A taglet option is empty. Ignore this option.");
4871                 }
4872             } else {
4873                 addArgIfNotEmpty(arguments, "-taglet", JavadocUtil.quotedArgument(taglet1.getTagletClass()));
4874             }
4875         }
4876     }
4877 
4878     /**
4879      * Auto-detect taglets class name from <code>tagletArtifacts</code> and add them to arguments.
4880      *
4881      * @param arguments not null
4882      * @throws MavenReportException if any
4883      * @see JavadocUtil#getTagletClassNames(File)
4884      */
4885     private void addTagletsFromTagletArtifacts(List<String> arguments) throws MavenReportException {
4886         Set<TagletArtifact> tArtifacts = new LinkedHashSet<>();
4887         if (tagletArtifacts != null && tagletArtifacts.length > 0) {
4888             tArtifacts.addAll(Arrays.asList(tagletArtifacts));
4889         }
4890 
4891         if (includeDependencySources) {
4892             try {
4893                 resolveDependencyBundles();
4894             } catch (IOException e) {
4895                 throw new MavenReportException(
4896                         "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e);
4897             }
4898 
4899             if (isNotEmpty(dependencyJavadocBundles)) {
4900                 for (JavadocBundle bundle : dependencyJavadocBundles) {
4901                     JavadocOptions options = bundle.getOptions();
4902                     if (options != null && isNotEmpty(options.getTagletArtifacts())) {
4903                         tArtifacts.addAll(options.getTagletArtifacts());
4904                     }
4905                 }
4906             }
4907         }
4908 
4909         if (isEmpty(tArtifacts)) {
4910             return;
4911         }
4912 
4913         List<String> tagletsPath = new ArrayList<>();
4914 
4915         for (TagletArtifact aTagletArtifact : tArtifacts) {
4916             if ((StringUtils.isNotEmpty(aTagletArtifact.getGroupId()))
4917                     && (StringUtils.isNotEmpty(aTagletArtifact.getArtifactId()))
4918                     && (StringUtils.isNotEmpty(aTagletArtifact.getVersion()))) {
4919                 Artifact artifact;
4920                 try {
4921                     artifact = createAndResolveArtifact(aTagletArtifact);
4922                 } catch (ArtifactResolutionException e) {
4923                     throw new MavenReportException("Unable to resolve artifact:" + aTagletArtifact, e);
4924                 }
4925 
4926                 tagletsPath.add(artifact.getFile().getAbsolutePath());
4927             }
4928         }
4929 
4930         tagletsPath = JavadocUtil.pruneFiles(tagletsPath);
4931 
4932         for (String tagletJar : tagletsPath) {
4933             if (!tagletJar.toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
4934                 continue;
4935             }
4936 
4937             List<String> tagletClasses;
4938             try {
4939                 tagletClasses = JavadocUtil.getTagletClassNames(new File(tagletJar));
4940             } catch (IOException e) {
4941                 if (getLog().isWarnEnabled()) {
4942                     getLog().warn("Unable to auto-detect Taglet class names from '" + tagletJar
4943                             + "'. Try to specify them with <taglets/>.");
4944                 }
4945                 if (getLog().isDebugEnabled()) {
4946                     getLog().debug("IOException: " + e.getMessage(), e);
4947                 }
4948                 continue;
4949             } catch (ClassNotFoundException e) {
4950                 if (getLog().isWarnEnabled()) {
4951                     getLog().warn("Unable to auto-detect Taglet class names from '" + tagletJar
4952                             + "'. Try to specify them with <taglets/>.");
4953                 }
4954                 if (getLog().isDebugEnabled()) {
4955                     getLog().debug("ClassNotFoundException: " + e.getMessage(), e);
4956                 }
4957                 continue;
4958             } catch (NoClassDefFoundError e) {
4959                 if (getLog().isWarnEnabled()) {
4960                     getLog().warn("Unable to auto-detect Taglet class names from '" + tagletJar
4961                             + "'. Try to specify them with <taglets/>.");
4962                 }
4963                 if (getLog().isDebugEnabled()) {
4964                     getLog().debug("NoClassDefFoundError: " + e.getMessage(), e);
4965                 }
4966                 continue;
4967             }
4968 
4969             if (tagletClasses != null && !tagletClasses.isEmpty()) {
4970                 for (String tagletClass : tagletClasses) {
4971                     addArgIfNotEmpty(arguments, "-taglet", JavadocUtil.quotedArgument(tagletClass));
4972                 }
4973             }
4974         }
4975     }
4976 
4977     /**
4978      * Execute the Javadoc command line
4979      *
4980      * @param cmd                    not null
4981      * @param javadocOutputDirectory not null
4982      * @throws MavenReportException if any errors occur
4983      */
4984     private void executeJavadocCommandLine(Commandline cmd, File javadocOutputDirectory) throws MavenReportException {
4985         if (staleDataPath != null) {
4986             if (!isUpToDate(cmd)) {
4987                 doExecuteJavadocCommandLine(cmd, javadocOutputDirectory);
4988                 StaleHelper.writeStaleData(cmd, staleDataPath.toPath());
4989             }
4990         } else {
4991             doExecuteJavadocCommandLine(cmd, javadocOutputDirectory);
4992         }
4993     }
4994 
4995     /**
4996      * Check if the javadoc is uptodate or not
4997      *
4998      * @param cmd                    not null
4999      * @return <code>true</code> is the javadoc is uptodate, <code>false</code> otherwise
5000      * @throws MavenReportException  if any error occur
5001      */
5002     private boolean isUpToDate(Commandline cmd) throws MavenReportException {
5003         try {
5004             List<String> curdata = StaleHelper.getStaleData(cmd);
5005             Path cacheData = staleDataPath.toPath();
5006             List<String> prvdata;
5007             if (Files.isRegularFile(cacheData)) {
5008                 prvdata = Files.lines(cacheData, StandardCharsets.UTF_8).collect(Collectors.toList());
5009             } else {
5010                 prvdata = null;
5011             }
5012             if (curdata.equals(prvdata)) {
5013                 getLog().debug("Skipping javadoc generation, everything is up to date.");
5014                 return true;
5015             } else {
5016                 if (prvdata == null) {
5017                     getLog().debug("No previous run data found, generating javadoc.");
5018                 } else {
5019                     getLog().debug("Configuration changed, re-generating javadoc.");
5020                     if (getLog().isDebugEnabled()) {
5021                         List<String> newStrings = new ArrayList<>(curdata);
5022                         List<String> remStrings = new ArrayList<>(prvdata);
5023                         newStrings.removeAll(prvdata);
5024                         remStrings.removeAll(curdata);
5025                         if (!remStrings.isEmpty()) {
5026                             getLog().debug("     Removed: " + String.join(", ", remStrings));
5027                         }
5028                         if (!newStrings.isEmpty()) {
5029                             getLog().debug("     Added: " + String.join(", ", newStrings));
5030                         }
5031                     }
5032                 }
5033             }
5034         } catch (IOException e) {
5035             throw new MavenReportException("Error checking uptodate status", e);
5036         }
5037         return false;
5038     }
5039 
5040     /**
5041      * Execute the Javadoc command line
5042      *
5043      * @param cmd                    not null
5044      * @param javadocOutputDirectory not null
5045      * @throws MavenReportException if any errors occur
5046      */
5047     private void doExecuteJavadocCommandLine(Commandline cmd, File javadocOutputDirectory) throws MavenReportException {
5048         if (getLog().isDebugEnabled()) {
5049             // no quoted arguments
5050             getLog().debug(CommandLineUtils.toString(cmd.getCommandline()).replaceAll("'", ""));
5051         }
5052 
5053         String cmdLine = null;
5054         if (debug) {
5055             cmdLine = CommandLineUtils.toString(cmd.getCommandline()).replaceAll("'", "");
5056 
5057             writeDebugJavadocScript(cmdLine, javadocOutputDirectory);
5058         }
5059 
5060         CommandLineUtils.StringStreamConsumer err = new JavadocUtil.JavadocOutputStreamConsumer();
5061         CommandLineUtils.StringStreamConsumer out = new JavadocUtil.JavadocOutputStreamConsumer();
5062         try {
5063             int exitCode = CommandLineUtils.executeCommandLine(cmd, out, err);
5064 
5065             String output = StringUtils.isEmpty(out.getOutput())
5066                     ? null
5067                     : '\n' + out.getOutput().trim();
5068 
5069             if (exitCode != 0) {
5070                 if (cmdLine == null) {
5071                     cmdLine = CommandLineUtils.toString(cmd.getCommandline()).replaceAll("'", "");
5072                 }
5073                 writeDebugJavadocScript(cmdLine, javadocOutputDirectory);
5074 
5075                 if ((output != null && !output.isEmpty())
5076                         && StringUtils.isEmpty(err.getOutput())
5077                         && isJavadocVMInitError(output)) {
5078                     throw new MavenReportException(output + '\n' + '\n' + JavadocUtil.ERROR_INIT_VM + '\n'
5079                             + "Or, try to reduce the Java heap size for the Javadoc goal using "
5080                             + "-Dminmemory=<size> and -Dmaxmemory=<size>." + '\n' + '\n' + "Command line was: "
5081                             + cmdLine
5082                             + '\n' + '\n' + "Refer to the generated Javadoc files in '" + javadocOutputDirectory
5083                             + "' dir.\n");
5084                 }
5085 
5086                 if (output != null && !output.isEmpty()) {
5087                     getLog().info(output);
5088                 }
5089 
5090                 StringBuilder msg = new StringBuilder("\nExit code: ");
5091                 msg.append(exitCode);
5092                 if (StringUtils.isNotEmpty(err.getOutput())) {
5093                     // parse stderr, log informational output, add all other to exception message
5094                     List<String> nonInfoLines = new ArrayList<>();
5095                     for (String str : err.getOutput().split("\\R")) {
5096                         if (isInformationalOutput(str)) {
5097                             getLog().debug(str);
5098                         } else {
5099                             nonInfoLines.add(str);
5100                         }
5101                     }
5102                     if (!nonInfoLines.isEmpty()) {
5103                         msg.append('\n'); // new line between exit code and warnings/errors
5104                         msg.append(String.join("\n", nonInfoLines));
5105                     }
5106                 }
5107                 msg.append('\n');
5108                 msg.append("Command line was: ").append(cmdLine).append('\n').append('\n');
5109 
5110                 msg.append("Refer to the generated Javadoc files in '")
5111                         .append(javadocOutputDirectory)
5112                         .append("' dir.\n");
5113 
5114                 throw new MavenReportException(msg.toString());
5115             }
5116 
5117             if (output != null && !output.isEmpty()) {
5118                 getLog().info(output);
5119             }
5120         } catch (CommandLineException e) {
5121             throw new MavenReportException("Unable to execute javadoc command: " + e.getMessage(), e);
5122         }
5123 
5124         // ----------------------------------------------------------------------
5125         // Handle Javadoc warnings
5126         // ----------------------------------------------------------------------
5127 
5128         if (containsWarnings(err.getOutput())) {
5129             if (getLog().isWarnEnabled()) {
5130                 getLog().warn("Javadoc Warnings");
5131 
5132                 StringTokenizer token = new StringTokenizer(err.getOutput(), "\n");
5133                 while (token.hasMoreTokens()) {
5134                     String current = token.nextToken().trim();
5135 
5136                     // log informational output at debug level only
5137                     if (isInformationalOutput(current)) {
5138                         getLog().debug(current);
5139                     } else {
5140                         getLog().warn(current);
5141                     }
5142                 }
5143             }
5144 
5145             if (failOnWarnings) {
5146                 throw new MavenReportException("Project contains Javadoc Warnings");
5147             }
5148         }
5149     }
5150 
5151     private boolean containsWarnings(String output) {
5152         // JDK-8268774 / JDK-8270831
5153         if (this.javadocRuntimeVersion.isBefore("17")) {
5154             return output != null && !output.isEmpty();
5155         } else {
5156             return Arrays.stream(output.split("\\R"))
5157                     .reduce((first, second) -> second) // last line
5158                     .filter(line -> line.matches("\\d+ warnings?"))
5159                     .isPresent();
5160         }
5161     }
5162 
5163     /**
5164      * Determines whether the specified string is informational output of the Javadoc tool.<br/>
5165      * Such output should not be included as exception message or logged as warning or error.
5166      * <p>
5167      * The following texts are either hardcoded in the tool or can be found in versions of the
5168      * javadoc tool's English resource bundle of JDK 11 (and presumably later versions).<br/>
5169      * This method will neither help nor harm for localized (non-English) versions of the tool.
5170      * </p>
5171      *
5172      * @param str string to check
5173      * @return true if informational output, false if not or cannot be determined
5174      */
5175     private boolean isInformationalOutput(String str) {
5176         return str == null
5177                 || str.trim().isEmpty()
5178                 || str.startsWith("Loading source files for package ") // main.Loading_source_files_for_package
5179                 || str.startsWith("Loading source file ") // main.Loading_source_file
5180                 || str.startsWith("Generating ")
5181                 || str.startsWith("Constructing Javadoc information") // main.Building_tree
5182                 || str.startsWith("Building index for ")
5183                 || str.startsWith("Building tree for ")
5184                 || str.startsWith("Standard Doclet version ");
5185     }
5186 
5187     /**
5188      * @param outputFile        not null
5189      * @param inputResourceName a not null resource in <code>src/main/java</code>, <code>src/main/resources</code> or
5190      *                          <code>src/main/javadoc</code> or in the Javadoc plugin dependencies.
5191      * @return the resource file absolute path as String
5192      * @since 2.6
5193      */
5194     private Optional<File> getResource(File outputFile, String inputResourceName) {
5195         if (inputResourceName.startsWith("/")) {
5196             inputResourceName = inputResourceName.replaceFirst("//*", "");
5197         }
5198 
5199         List<String> classPath = new ArrayList<>();
5200         classPath.add(project.getBuild().getSourceDirectory());
5201 
5202         URL resourceURL = getResource(classPath, inputResourceName);
5203         if (resourceURL != null) {
5204             getLog().debug(inputResourceName + " found in the main src directory of the project.");
5205             return Optional.of(FileUtils.toFile(resourceURL));
5206         }
5207 
5208         classPath.clear();
5209         List<Resource> resources = project.getBuild().getResources();
5210         for (Resource resource : resources) {
5211             classPath.add(resource.getDirectory());
5212         }
5213         resourceURL = getResource(classPath, inputResourceName);
5214         if (resourceURL != null) {
5215             getLog().debug(inputResourceName + " found in the main resources directories of the project.");
5216             return Optional.of(FileUtils.toFile(resourceURL));
5217         }
5218 
5219         if (javadocDirectory.exists()) {
5220             classPath.clear();
5221             classPath.add(javadocDirectory.getAbsolutePath());
5222             resourceURL = getResource(classPath, inputResourceName);
5223             if (resourceURL != null) {
5224                 getLog().debug(inputResourceName + " found in the main javadoc directory of the project.");
5225                 return Optional.of(FileUtils.toFile(resourceURL));
5226             }
5227         }
5228 
5229         classPath.clear();
5230         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
5231         Plugin javadocPlugin = getPlugin(project, pluginId);
5232         if (javadocPlugin != null && javadocPlugin.getDependencies() != null) {
5233             List<Dependency> dependencies = javadocPlugin.getDependencies();
5234             for (Dependency dependency : dependencies) {
5235                 ResourcesArtifact resourceArtifact = new ResourcesArtifact();
5236                 resourceArtifact.setGroupId(dependency.getGroupId());
5237                 resourceArtifact.setArtifactId(dependency.getArtifactId());
5238                 resourceArtifact.setVersion(dependency.getVersion());
5239                 resourceArtifact.setClassifier(dependency.getClassifier());
5240                 Artifact artifact = null;
5241                 try {
5242                     artifact = createAndResolveArtifact(resourceArtifact);
5243                 } catch (Exception e) {
5244                     logError("Unable to retrieve the dependency: " + dependency + ". Ignored.", e);
5245                 }
5246 
5247                 if (artifact != null && artifact.getFile().exists()) {
5248                     classPath.add(artifact.getFile().getAbsolutePath());
5249                 }
5250             }
5251             resourceURL = getResource(classPath, inputResourceName);
5252             if (resourceURL != null) {
5253                 getLog().debug(inputResourceName + " found in javadoc plugin dependencies.");
5254                 try {
5255                     JavadocUtil.copyResource(resourceURL, outputFile);
5256 
5257                     return Optional.of(outputFile);
5258                 } catch (IOException e) {
5259                     logError("IOException: " + e.getMessage(), e);
5260                 }
5261             }
5262         }
5263 
5264         getLog().warn("Unable to find the resource '" + inputResourceName + "'. Using default Javadoc resources.");
5265 
5266         return Optional.empty();
5267     }
5268 
5269     /**
5270      * @param classPath a not null String list of files where resource will be looked up
5271      * @param resource a not null resource to find in the class path
5272      * @return the resource from the given classpath or null if not found
5273      * @see ClassLoader#getResource(String)
5274      * @since 2.6
5275      */
5276     private URL getResource(final List<String> classPath, final String resource) {
5277         List<URL> urls = new ArrayList<>(classPath.size());
5278         for (String filename : classPath) {
5279             try {
5280                 urls.add(new File(filename).toURI().toURL());
5281             } catch (MalformedURLException | IllegalArgumentException e) {
5282                 getLog().error("MalformedURLException: " + e.getMessage());
5283             }
5284         }
5285 
5286         URLClassLoader javadocClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), null);
5287         try {
5288             return javadocClassLoader.getResource(resource);
5289         } finally {
5290             try {
5291                 javadocClassLoader.close();
5292             } catch (IOException ex) {
5293                 // ignore
5294             }
5295         }
5296     }
5297 
5298     /**
5299      * Get the full javadoc goal. Loads the plugin's pom.properties to get the current plugin version.
5300      *
5301      * @return <code>org.apache.maven.plugins:maven-javadoc-plugin:CURRENT_VERSION:[test-]javadoc</code>
5302      */
5303     private String getFullJavadocGoal() {
5304         String javadocPluginVersion = null;
5305         String resource = "META-INF/maven/org.apache.maven.plugins/maven-javadoc-plugin/pom.properties";
5306         try (InputStream resourceAsStream =
5307                 AbstractJavadocMojo.class.getClassLoader().getResourceAsStream(resource)) {
5308             if (resourceAsStream != null) {
5309                 Properties properties = new Properties();
5310                 properties.load(resourceAsStream);
5311                 if (StringUtils.isNotEmpty(properties.getProperty("version"))) {
5312                     javadocPluginVersion = properties.getProperty("version");
5313                 }
5314             }
5315         } catch (IOException e) {
5316             // nop
5317         }
5318 
5319         StringBuilder sb = new StringBuilder();
5320 
5321         sb.append("org.apache.maven.plugins:maven-javadoc-plugin:");
5322         if (javadocPluginVersion != null && !javadocPluginVersion.isEmpty()) {
5323             sb.append(javadocPluginVersion).append(":");
5324         }
5325 
5326         if (this instanceof TestJavadocReport) {
5327             sb.append("test-javadoc");
5328         } else {
5329             sb.append("javadoc");
5330         }
5331 
5332         return sb.toString();
5333     }
5334 
5335     protected boolean isDetectOfflineLinks() {
5336         return detectOfflineLinks;
5337     }
5338 
5339     /**
5340      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
5341      *
5342      * @return the detected Javadoc links using the Maven conventions for all modules defined in the current project
5343      *         or an empty list
5344      * @throws MavenReportException if any
5345      * @see #detectOfflineLinks
5346      * @see #reactorProjects
5347      * @since 2.6
5348      */
5349     private List<OfflineLink> getModulesLinks() throws MavenReportException {
5350         List<MavenProject> aggregatedProjects = reactorProjects;
5351         if (!isDetectOfflineLinks() || isAggregator() || aggregatedProjects.isEmpty()) {
5352             return Collections.emptyList();
5353         }
5354 
5355         getLog().debug("Trying to add links for modules...");
5356 
5357         Set<String> dependencyArtifactIds = new HashSet<>();
5358         final Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
5359         for (Artifact artifact : dependencyArtifacts) {
5360             dependencyArtifactIds.add(artifact.getId());
5361         }
5362 
5363         List<OfflineLink> modulesLinks = new ArrayList<>();
5364         String javadocDirRelative = PathUtils.toRelative(project.getBasedir(), getPluginReportOutputDirectory());
5365         for (MavenProject p : aggregatedProjects) {
5366             if (!dependencyArtifactIds.contains(p.getArtifact().getId()) || (p.getUrl() == null)) {
5367                 continue;
5368             }
5369 
5370             File location = new File(p.getBasedir(), javadocDirRelative);
5371 
5372             if (!location.exists()) {
5373                 String javadocGoal = getFullJavadocGoal();
5374                 if (getLog().isDebugEnabled()) {
5375                     getLog().debug("Javadoc directory not found: " + location);
5376                     getLog().debug("The goal '" + javadocGoal + "' has not been previously called for the module: '"
5377                             + p.getId() + "'. Trying to invoke it...");
5378                 }
5379 
5380                 File invokerDir = new File(project.getBuild().getDirectory(), "invoker");
5381                 invokerDir.mkdirs();
5382                 File invokerLogFile = FileUtils.createTempFile("maven-javadoc-plugin", ".txt", invokerDir);
5383                 try {
5384                     JavadocUtil.invokeMaven(
5385                             getLog(),
5386                             session.getRepositorySession().getLocalRepository().getBasedir(),
5387                             p.getFile(),
5388                             Collections.singletonList(javadocGoal),
5389                             null,
5390                             invokerLogFile,
5391                             session.getRequest().getGlobalSettingsFile(),
5392                             session.getRequest().getUserSettingsFile(),
5393                             session.getRequest().getGlobalToolchainsFile(),
5394                             session.getRequest().getUserToolchainsFile());
5395                 } catch (MavenInvocationException e) {
5396                     logError("MavenInvocationException: " + e.getMessage(), e);
5397 
5398                     try {
5399                         String invokerLogContent =
5400                                 new String(Files.readAllBytes(invokerLogFile.toPath()), StandardCharsets.UTF_8);
5401                         // TODO: Why are we only interested in cases where the JVM won't start?
5402                         // probably we should throw an error in all cases
5403                         // [MJAVADOC-275][jdcasey] I changed the logic here to only throw an error WHEN
5404                         //   the JVM won't start (opposite of what it was).
5405                         if (invokerLogContent.contains(JavadocUtil.ERROR_INIT_VM)) {
5406                             throw new MavenReportException(e.getMessage(), e);
5407                         }
5408                     } catch (IOException ex) {
5409                         // ignore
5410                     }
5411                 } finally {
5412                     // just create the directory to prevent repeated invocations.
5413                     if (!location.exists()) {
5414                         getLog().warn("Creating fake javadoc directory to prevent repeated invocations: " + location);
5415                         location.mkdirs();
5416                     }
5417                 }
5418             }
5419 
5420             if (location.exists()) {
5421                 String url = getJavadocLink(p);
5422 
5423                 OfflineLink ol = new OfflineLink();
5424                 ol.setUrl(url);
5425                 ol.setLocation(location.getAbsolutePath());
5426 
5427                 if (getLog().isDebugEnabled()) {
5428                     getLog().debug("Added Javadoc offline link: " + url + " for the module: " + p.getId());
5429                 }
5430 
5431                 modulesLinks.add(ol);
5432             }
5433         }
5434 
5435         return modulesLinks;
5436     }
5437 
5438     /**
5439      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
5440      *
5441      * @return the detected Javadoc links using the Maven conventions for all dependencies defined in the current
5442      *         project or an empty list.
5443      * @see #detectLinks
5444      * @see #isValidJavadocLink(String, boolean)
5445      * @since 2.6
5446      */
5447     private List<String> getDependenciesLinks() {
5448         if (!detectLinks) {
5449             return Collections.emptyList();
5450         }
5451 
5452         getLog().debug("Trying to add links for dependencies...");
5453 
5454         List<String> dependenciesLinks = new ArrayList<>();
5455 
5456         final Set<Artifact> dependencies = project.getDependencyArtifacts();
5457         for (Artifact artifact : dependencies) {
5458             if (artifact.getFile() == null || !artifact.getFile().exists()) {
5459                 continue;
5460             }
5461 
5462             Optional<DependencyLink> depLink = this.dependencyLinks.stream()
5463                     .filter(d -> matches(d, artifact))
5464                     .findAny();
5465 
5466             final String url;
5467             final boolean detected;
5468             if (depLink.isPresent()) {
5469                 url = depLink.get().getUrl();
5470                 detected = false;
5471             } else {
5472                 try {
5473                     MavenProject artifactProject = mavenProjectBuilder
5474                             .build(artifact, getProjectBuildingRequest(project))
5475                             .getProject();
5476 
5477                     url = getJavadocLink(artifactProject);
5478                     detected = true;
5479                 } catch (ProjectBuildingException e) {
5480                     logError("ProjectBuildingException for " + artifact + ": " + e.getMessage(), e);
5481                     continue;
5482                 }
5483             }
5484 
5485             if (url != null && isValidJavadocLink(url, detected)) {
5486                 getLog().debug("Added Javadoc link: " + url + " for " + artifact.getId());
5487 
5488                 dependenciesLinks.add(url);
5489             }
5490         }
5491 
5492         return dependenciesLinks;
5493     }
5494 
5495     private boolean matches(DependencyLink d, Artifact artifact) {
5496         if (d.getGroupId() != null && !d.getGroupId().equals(artifact.getGroupId())) {
5497             return false;
5498         }
5499         if (d.getArtifactId() != null && !d.getArtifactId().equals(artifact.getArtifactId())) {
5500             return false;
5501         }
5502         if (d.getClassifier() != null && !d.getClassifier().equals(artifact.getClassifier())) {
5503             return false;
5504         }
5505         return true;
5506     }
5507 
5508     /**
5509      * @return if {@code detectJavaApiLink}, the Java API link based on the {@code javaApiLinks} properties and the
5510      *         value of the <code>source</code> parameter in the
5511      *         <code>org.apache.maven.plugins:maven-compiler-plugin</code>
5512      *         defined in <code>${project.build.plugins}</code> or in <code>${project.build.pluginManagement}</code>,
5513      *         or the {@code javadocRuntimeVersion}, or <code>null</code> if not defined.
5514      * @see <a href="http://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source">source parameter</a>
5515      * @since 2.6
5516      */
5517     protected final OfflineLink getDefaultJavadocApiLink() {
5518         if (!detectJavaApiLink) {
5519             return null;
5520         }
5521 
5522         final JavaVersion javaApiversion;
5523         if (release != null) {
5524             javaApiversion = JavaVersion.parse(release);
5525         } else if (source != null && !source.isEmpty()) {
5526             javaApiversion = JavaVersion.parse(source);
5527         } else {
5528             final String pluginId = "org.apache.maven.plugins:maven-compiler-plugin";
5529             String sourceConfigured = getPluginParameter(project, pluginId, "source");
5530             if (sourceConfigured != null) {
5531                 javaApiversion = JavaVersion.parse(sourceConfigured);
5532             } else {
5533                 getLog().debug("No maven-compiler-plugin defined in ${build.plugins} or in "
5534                         + "${project.build.pluginManagement} for the " + project.getId()
5535                         + ". Added Javadoc API link according the javadoc executable version i.e.: "
5536                         + javadocRuntimeVersion);
5537 
5538                 javaApiversion = javadocRuntimeVersion;
5539             }
5540         }
5541 
5542         final String javaApiKey;
5543         if (javaApiversion.asMajor().isAtLeast("9")) {
5544             javaApiKey = "api_" + javaApiversion.asMajor();
5545         } else {
5546             javaApiKey = "api_1." + javaApiversion.asMajor().toString().charAt(0);
5547         }
5548 
5549         final String javaApiLink;
5550         if (javaApiLinks != null && javaApiLinks.containsKey(javaApiKey)) {
5551             javaApiLink = javaApiLinks.getProperty(javaApiKey);
5552         } else if (javaApiversion.isAtLeast("16")) {
5553             javaApiLink = null; // JDK-8216497
5554         } else if (javaApiversion.isAtLeast("11")) {
5555             javaApiLink =
5556                     String.format("https://docs.oracle.com/en/java/javase/%s/docs/api/", javaApiversion.getValue(1));
5557         } else if (javaApiversion.asMajor().isAtLeast("6")) {
5558             javaApiLink = String.format(
5559                     "https://docs.oracle.com/javase/%s/docs/api/",
5560                     javaApiversion.asMajor().getValue(1));
5561         } else if (javaApiversion.isAtLeast("1.5")) {
5562             javaApiLink = "https://docs.oracle.com/javase/1.5.0/docs/api/";
5563         } else {
5564             javaApiLink = null;
5565         }
5566 
5567         if (getLog().isDebugEnabled()) {
5568             if (javaApiLink != null) {
5569                 getLog().debug("Found Java API link: " + javaApiLink);
5570             } else {
5571                 getLog().debug("No Java API link found.");
5572             }
5573         }
5574 
5575         if (javaApiLink == null) {
5576             return null;
5577         }
5578 
5579         final Path javaApiListFile;
5580         final String resourceName;
5581         if (javaApiversion.isAtLeast("10")) {
5582             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve("element-list");
5583             resourceName = "java-api-element-list-" + javaApiversion.toString().substring(0, 2);
5584         } else if (javaApiversion.asMajor().isAtLeast("9")) {
5585             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve("package-list");
5586             resourceName = "java-api-package-list-9";
5587         } else {
5588             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve("package-list");
5589             resourceName = "java-api-package-list-1."
5590                     + javaApiversion.asMajor().toString().charAt(0);
5591         }
5592 
5593         OfflineLink link = new OfflineLink();
5594         link.setLocation(javaApiListFile.getParent().toAbsolutePath().toString());
5595         link.setUrl(javaApiLink);
5596 
5597         InputStream in = this.getClass().getResourceAsStream(resourceName);
5598         if (in != null) {
5599             try (InputStream closableIS = in) {
5600                 // TODO only copy when changed
5601                 Files.copy(closableIS, javaApiListFile, StandardCopyOption.REPLACE_EXISTING);
5602             } catch (IOException ioe) {
5603                 logError("Can't get " + resourceName + ": " + ioe.getMessage(), ioe);
5604                 return null;
5605             }
5606         }
5607 
5608         return link;
5609     }
5610 
5611     /**
5612      * Follows the given links if the Javadoc version is before 12, and returns their last
5613      * redirect locations. Ordering is kept. This is necessary because the javadoc tool doesn't follow
5614      * links, see JDK-8190312 (MJAVADOC-427, MJAVADOC-487)
5615      *
5616      * @param links Links to follow=
5617      * @return last redirect location of all the links
5618      */
5619     private Set<String> followLinks(Set<String> links) {
5620         if (javadocRuntimeVersion.isAtLeast("12")) {
5621             return links;
5622         }
5623         Set<String> redirectLinks = new LinkedHashSet<>(links.size());
5624         for (String link : links) {
5625             try {
5626                 redirectLinks.add(
5627                         JavadocUtil.getRedirectUrl(new URL(link), settings).toString());
5628             } catch (MalformedURLException | IllegalArgumentException e) {
5629                 // only print in debug, it should have been logged already in warn/error because link isn't valid
5630                 getLog().debug("Could not follow " + link + ". Reason: " + e.getMessage());
5631             } catch (IOException e) {
5632                 // only print in debug, it should have been logged already in warn/error because link isn't valid
5633                 getLog().debug("Could not follow " + link + ". Reason: " + e.getMessage());
5634 
5635                 // Even when link produces error it should be kept in the set because the error might be caused by
5636                 // incomplete redirect configuration on the server side.
5637                 // This partially restores the previous behaviour before fix for MJAVADOC-427
5638                 redirectLinks.add(link);
5639             }
5640         }
5641         return redirectLinks;
5642     }
5643 
5644     /**
5645      * @param link not null
5646      * @param detecting <code>true</code> if the link is generated by
5647      * <code>detectLinks</code>, or <code>false</code> otherwise
5648      * @return <code>true</code> if the link has a <code>/package-list</code>, <code>false</code> otherwise.
5649      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#package-list">
5650      *      package-list spec</a>
5651      * @since 2.6
5652      */
5653     protected boolean isValidJavadocLink(String link, boolean detecting) {
5654         try {
5655             final URI packageListUri;
5656             final URI elementListUri;
5657 
5658             if (link.trim().toLowerCase(Locale.ENGLISH).startsWith("http:")
5659                     || link.trim().toLowerCase(Locale.ENGLISH).startsWith("https:")
5660                     || link.trim().toLowerCase(Locale.ENGLISH).startsWith("ftp:")
5661                     || link.trim().toLowerCase(Locale.ENGLISH).startsWith("file:")) {
5662                 packageListUri = new URI(link + '/' + PACKAGE_LIST);
5663                 elementListUri = new URI(link + '/' + ELEMENT_LIST);
5664             } else {
5665                 // links can be relative paths or files
5666                 File dir = new File(link);
5667                 if (!dir.isAbsolute()) {
5668                     dir = new File(getPluginReportOutputDirectory(), link);
5669                 }
5670                 if (!dir.isDirectory()) {
5671                     if (detecting) {
5672                         getLog().warn("The given File link: " + dir + " is not a dir.");
5673                     } else {
5674                         getLog().error("The given File link: " + dir + " is not a dir.");
5675                     }
5676                 }
5677                 packageListUri = new File(dir, PACKAGE_LIST).toURI();
5678                 elementListUri = new File(dir, ELEMENT_LIST).toURI();
5679             }
5680 
5681             try {
5682                 if (JavadocUtil.isValidElementList(elementListUri.toURL(), settings, validateLinks)) {
5683                     return true;
5684                 }
5685             } catch (IOException e) {
5686                 // ignore this because it is optional
5687             }
5688 
5689             if (JavadocUtil.isValidPackageList(packageListUri.toURL(), settings, validateLinks)) {
5690                 return true;
5691             }
5692 
5693             if (getLog().isErrorEnabled()) {
5694                 if (detecting) {
5695                     getLog().warn("Invalid links: " + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST
5696                             + ". Ignored it.");
5697                 } else {
5698                     getLog().error("Invalid links: " + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST
5699                             + ". Ignored it.");
5700                 }
5701             }
5702 
5703             return false;
5704         } catch (URISyntaxException | MalformedURLException | IllegalArgumentException e) {
5705             if (getLog().isErrorEnabled()) {
5706                 if (detecting) {
5707                     getLog().warn("Malformed link: " + e.getMessage() + ". Ignored it.");
5708                 } else {
5709                     getLog().error("Malformed link: " + e.getMessage() + ". Ignored it.");
5710                 }
5711             }
5712             return false;
5713         } catch (IOException e) {
5714             if (getLog().isErrorEnabled()) {
5715                 if (detecting) {
5716                     getLog().warn("Error fetching link: " + link + ". Ignored it.");
5717                 } else {
5718                     getLog().error("Error fetching link: " + link + ". Ignored it.");
5719                 }
5720             }
5721             return false;
5722         }
5723     }
5724 
5725     /**
5726      * Write a debug javadoc script in case of command line error or in debug mode.
5727      *
5728      * @param cmdLine                the current command line as string, not null.
5729      * @param javadocOutputDirectory the output dir, not null.
5730      * @see #executeJavadocCommandLine(Commandline, File)
5731      * @since 2.6
5732      */
5733     private void writeDebugJavadocScript(String cmdLine, File javadocOutputDirectory) {
5734         File commandLineFile = new File(javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME);
5735         commandLineFile.getParentFile().mkdirs();
5736 
5737         try {
5738             FileUtils.fileWrite(commandLineFile.getAbsolutePath(), null /* platform encoding */, cmdLine);
5739 
5740             if (!SystemUtils.IS_OS_WINDOWS) {
5741                 Runtime.getRuntime().exec(new String[] {"chmod", "a+x", commandLineFile.getAbsolutePath()});
5742             }
5743         } catch (IOException e) {
5744             logError("Unable to write '" + commandLineFile.getName() + "' debug script file", e);
5745         }
5746     }
5747 
5748     /**
5749      * Check if the Javadoc JVM is correctly started or not.
5750      *
5751      * @param output the command line output, not null.
5752      * @return <code>true</code> if Javadoc output command line contains Javadoc word, <code>false</code> otherwise.
5753      * @see #executeJavadocCommandLine(Commandline, File)
5754      * @since 2.6.1
5755      */
5756     private boolean isJavadocVMInitError(String output) {
5757         /*
5758          * see main.usage and main.Building_tree keys from
5759          * com.sun.tools.javadoc.resources.javadoc bundle in tools.jar
5760          */
5761         return !(output.contains("Javadoc") || output.contains("javadoc"));
5762     }
5763 
5764     // ----------------------------------------------------------------------
5765     // Static methods
5766     // ----------------------------------------------------------------------
5767 
5768     /**
5769      * @param project not null
5770      * @return the javadoc link based on the project URL i.e. <code>${project.url}/apidocs</code>.
5771      * @since 2.6
5772      */
5773     private static String getJavadocLink(MavenProject project) {
5774         if (project.getUrl() == null) {
5775             return null;
5776         }
5777 
5778         String url = cleanUrl(project.getUrl());
5779 
5780         return url + "/apidocs";
5781     }
5782 
5783     /**
5784      * @param url could be null
5785      * @return the cleaned URL or empty if url was null
5786      * @since 2.6
5787      */
5788     private static String cleanUrl(String url) {
5789         if (url == null) {
5790             return "";
5791         }
5792 
5793         url = url.trim();
5794         while (url.endsWith("/")) {
5795             url = url.substring(0, url.lastIndexOf("/"));
5796         }
5797 
5798         return url;
5799     }
5800 
5801     /**
5802      * @param project        not null
5803      * @param pluginId not null key of the plugin defined in {@link org.apache.maven.model.Build#getPluginsAsMap()}
5804      *                 or in {@link org.apache.maven.model.PluginManagement#getPluginsAsMap()}
5805      * @return the Maven plugin defined in <code>${project.build.plugins}</code> or in
5806      *         <code>${project.build.pluginManagement}</code>, or <code>null</code> if not defined
5807      * @since 2.6
5808      */
5809     private static Plugin getPlugin(MavenProject project, String pluginId) {
5810         if ((project.getBuild() == null) || (project.getBuild().getPluginsAsMap() == null)) {
5811             return null;
5812         }
5813 
5814         Plugin plugin = project.getBuild().getPluginsAsMap().get(pluginId);
5815 
5816         if ((plugin == null)
5817                 && (project.getBuild().getPluginManagement() != null)
5818                 && (project.getBuild().getPluginManagement().getPluginsAsMap() != null)) {
5819             plugin = project.getBuild().getPluginManagement().getPluginsAsMap().get(pluginId);
5820         }
5821 
5822         return plugin;
5823     }
5824 
5825     /**
5826      * @param project the Maven project
5827      * @param pluginId not null
5828      * @param param    not null
5829      * @return the string parameter defined in the plugin configuration by <code>param</code> key
5830      *         or <code>null</code> if not found.
5831      * @since 2.6
5832      */
5833     private static String getPluginParameter(MavenProject project, String pluginId, String param) {
5834         Plugin plugin = getPlugin(project, pluginId);
5835         if (plugin != null) {
5836             Xpp3Dom xpp3Dom = (Xpp3Dom) plugin.getConfiguration();
5837             if (xpp3Dom != null
5838                     && xpp3Dom.getChild(param) != null
5839                     && StringUtils.isNotEmpty(xpp3Dom.getChild(param).getValue())) {
5840                 return xpp3Dom.getChild(param).getValue();
5841             }
5842         }
5843 
5844         return null;
5845     }
5846 
5847     /**
5848      * Construct the output file for the generated javadoc-options XML file, after creating the
5849      * javadocOptionsDir if necessary. This method does NOT write to the file in question.
5850      *
5851      * @return The options {@link File} file.
5852      * @since 2.7
5853      */
5854     protected final File getJavadocOptionsFile() {
5855         if (javadocOptionsDir != null && !javadocOptionsDir.exists()) {
5856             javadocOptionsDir.mkdirs();
5857         }
5858 
5859         return new File(javadocOptionsDir, "javadoc-options-" + getAttachmentClassifier() + ".xml");
5860     }
5861 
5862     /**
5863      * Generate a javadoc-options XML file, for either bundling with a javadoc-resources artifact OR
5864      * supplying to a distro module in a includeDependencySources configuration, so the javadoc options
5865      * from this execution can be reconstructed and merged in the distro build.
5866      *
5867      * @return {@link JavadocOptions}
5868      * @throws IOException {@link IOException}
5869      * @since 2.7
5870      */
5871     protected final JavadocOptions buildJavadocOptions() throws IOException {
5872         JavadocOptions options = new JavadocOptions();
5873 
5874         options.setBootclasspathArtifacts(toList(bootclasspathArtifacts));
5875         options.setDocfilesSubdirsUsed(docfilessubdirs);
5876         options.setDocletArtifacts(toList(docletArtifact, docletArtifacts));
5877         options.setExcludedDocfilesSubdirs(excludedocfilessubdir == null ? null : excludedocfilessubdir.trim());
5878         options.setExcludePackageNames(toList(excludePackageNames));
5879         options.setGroups(toList(groups));
5880         options.setLinks(links);
5881         options.setOfflineLinks(toList(offlineLinks));
5882         options.setResourcesArtifacts(toList(resourcesArtifacts));
5883         options.setTagletArtifacts(toList(tagletArtifact, tagletArtifacts));
5884         options.setTaglets(toList(taglets));
5885         options.setTags(toList(tags));
5886 
5887         if (getProject() != null && getJavadocDirectory() != null) {
5888             Path basedir = getProject().getBasedir().toPath();
5889             Path javadocDirectory = getJavadocDirectory().toPath().toAbsolutePath();
5890             Path javadocResourcesDirectory = basedir.relativize(javadocDirectory);
5891             options.setJavadocResourcesDirectory(javadocResourcesDirectory.toString());
5892         }
5893 
5894         File optionsFile = getJavadocOptionsFile();
5895 
5896         try (Writer writer = WriterFactory.newXmlWriter(optionsFile)) {
5897             new JavadocOptionsXpp3Writer().write(writer, options);
5898         }
5899 
5900         return options;
5901     }
5902 
5903     /**
5904      * Override this if you need to provide a bundle attachment classifier, as in the case of test
5905      * javadocs.
5906      *
5907      * @return the attachment classifier
5908      */
5909     protected String getAttachmentClassifier() {
5910         return JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER;
5911     }
5912 
5913     /**
5914      * Logs an error with throwable content only if in debug.
5915      *
5916      * @param message the message which should be announced
5917      * @param t the throwable part of the message
5918      */
5919     protected void logError(String message, Throwable t) {
5920         if (getLog().isDebugEnabled()) {
5921             getLog().error(message, t);
5922         } else {
5923             getLog().error(message);
5924         }
5925     }
5926 
5927     protected List<MavenProject> getReactorProjects() {
5928         return reactorProjects;
5929     }
5930 
5931     /**
5932      * @param prefix the prefix of the exception
5933      * @param e the exception
5934      * @throws MojoExecutionException {@link MojoExecutionException} issue while generating report
5935      */
5936     protected void failOnError(String prefix, Exception e) throws MojoExecutionException {
5937         if (failOnError) {
5938             if (e instanceof RuntimeException) {
5939                 throw (RuntimeException) e;
5940             }
5941             throw new MojoExecutionException(prefix + ": " + e.getMessage(), e);
5942         }
5943 
5944         getLog().error(prefix + ": " + e.getMessage(), e);
5945     }
5946 
5947     /**
5948      * @return list of projects to be part of aggregated javadoc
5949      */
5950     private List<MavenProject> getAggregatedProjects() {
5951         if (this.reactorProjects == null) {
5952             return Collections.emptyList();
5953         }
5954         Map<Path, MavenProject> reactorProjectsMap = new HashMap<>();
5955         for (MavenProject reactorProject : this.reactorProjects) {
5956             if (!isSkippedJavadoc(reactorProject)
5957                     && //
5958                     !isSkippedModule(reactorProject)) {
5959                 reactorProjectsMap.put(reactorProject.getBasedir().toPath(), reactorProject);
5960             }
5961         }
5962 
5963         return new ArrayList<>(modulesForAggregatedProject(project, reactorProjectsMap));
5964     }
5965 
5966     /**
5967      * @param mavenProject the project that might be skipped
5968      * @return <code>true</code> if the project needs to be skipped from aggregate generation
5969      */
5970     protected boolean isSkippedModule(MavenProject mavenProject) {
5971         if (this.skippedModules == null || this.skippedModules.isEmpty()) {
5972             return false;
5973         }
5974         if (this.patternsToSkip == null) {
5975             this.patternsToSkip = Arrays.stream(StringUtils.split(this.skippedModules, ','))
5976                     .map(String::trim)
5977                     // we are expecting something such [groupdId:]artifactId so if no groupId we want to match any
5978                     // groupId
5979                     .map(s -> !s.contains(":") ? ".*:" + s : s)
5980                     .map(Pattern::compile)
5981                     .collect(Collectors.toList());
5982         }
5983         Optional<Pattern> found = this.patternsToSkip.stream()
5984                 .filter(pattern -> pattern.matcher(mavenProject.getGroupId() + ":" + mavenProject.getArtifactId())
5985                         .matches())
5986                 .findAny();
5987         return found.isPresent() || isSkippedJavadoc(mavenProject);
5988     }
5989 
5990     /**
5991      * @param mavenProject the project that might be skipped
5992      * @return <code>true</code> if the pom configuration skips javadoc generation for the project
5993      */
5994     protected boolean isSkippedJavadoc(MavenProject mavenProject) {
5995         String property = mavenProject.getProperties().getProperty("maven.javadoc.skip");
5996         if (property != null) {
5997             boolean skip = BooleanUtils.toBoolean(property);
5998             getLog().debug("isSkippedJavadoc " + mavenProject + " " + skip);
5999             return skip;
6000         }
6001         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
6002         property = getPluginParameter(mavenProject, pluginId, "skip");
6003         if (property != null) {
6004             boolean skip = BooleanUtils.toBoolean(property);
6005             getLog().debug("isSkippedJavadoc " + mavenProject + " " + skip);
6006             return skip;
6007         }
6008         if (mavenProject.getParent() != null) {
6009             return isSkippedJavadoc(mavenProject.getParent());
6010         }
6011         getLog().debug("isSkippedJavadoc " + mavenProject + " " + false);
6012         return false;
6013     }
6014 }