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