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