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