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