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