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