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