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