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