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.util.ArrayList;
123 import java.util.Arrays;
124 import java.util.Calendar;
125 import java.util.Collection;
126 import java.util.Collections;
127 import java.util.Date;
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/>
1290      * See
1291      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#notimestamp">notimestamp</a>.
1292      * <br/>
1293      * Since
1294      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.5.0.html#commandlineoptions">
1295      * Java 5.0</a>.
1296      * <br/>
1297      *
1298      * @since 2.1
1299      */
1300     @Parameter( property = "notimestamp", defaultValue = "false" )
1301     private boolean notimestamp;
1302 
1303     /**
1304      * Omits the class/interface hierarchy pages from the generated docs.
1305      * <br>
1306      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#notree">notree</a> option
1307      */
1308     @Parameter( property = "notree", defaultValue = "false" )
1309     private boolean notree;
1310 
1311     /**
1312      * This option is a variation of <code>-link</code>; they both create links to javadoc-generated documentation
1313      * for external referenced classes.
1314      * <br/>
1315      * See
1316      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#linkoffline">linkoffline</a>.
1317      * <br/>
1318      * Example:
1319      * <pre>
1320      * &lt;offlineLinks&gt;
1321      *   &lt;offlineLink&gt;
1322      *     &lt;url&gt;https://docs.oracle.com/javase/1.5.0/docs/api/&lt;/url&gt;
1323      *     &lt;location&gt;../javadoc/jdk-5.0/&lt;/location&gt;
1324      *   &lt;/offlineLink&gt;
1325      * &lt;/offlineLinks&gt;
1326      * </pre>
1327      * <br/>
1328      * <b>Note</b>: if {@code detectOfflineLinks} is defined, the offline links between the project modules are
1329      * automatically added if the goal is calling in a non-aggregator way.
1330      * <br>
1331      * @see <a href="./apidocs/org/apache/maven/plugins/javadoc/options/OfflineLink.html">Javadoc</a>.
1332      */
1333     @Parameter( property = "offlineLinks" )
1334     private OfflineLink[] offlineLinks;
1335 
1336     /**
1337      * Specifies the destination directory where javadoc saves the generated HTML files.
1338      * <br>
1339      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#d">javadoc d</a>
1340      */
1341     @Parameter( property = "destDir", alias = "destDir", defaultValue = "${project.build.directory}/apidocs",
1342                     required = true )
1343     protected File outputDirectory;
1344 
1345     /**
1346      * Specify the text for upper left frame.
1347      * <br/>
1348      * Since
1349      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.2.html#commandlineoptions">
1350      * Java 1.4.2</a>.
1351      *
1352      * @since 2.1
1353      */
1354     @Parameter( property = "packagesheader" )
1355     private String packagesheader;
1356 
1357     /**
1358      * Generates compile-time warnings for missing serial tags.
1359      * <br/>
1360      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#serialwarn">serialwarn</a> option
1361      */
1362     @Parameter( property = "serialwarn", defaultValue = "false" )
1363     private boolean serialwarn;
1364 
1365     /**
1366      * Specify the number of spaces each tab takes up in the source. If no tab is used in source, the default
1367      * space is used.
1368      * <br/>
1369      * Note: was <code>linksourcetab</code> in Java 1.4.2 (refer to bug ID
1370      * <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4788919">4788919</a>).
1371      * <br/>
1372      * Since
1373      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.2.html#commandlineoptions">
1374      * 1.4.2</a>.
1375      * <br/>
1376      * Since Java 5.0.
1377      *
1378      * @since 2.1
1379      */
1380     @Parameter( property = "sourcetab", alias = "linksourcetab" )
1381     private int sourcetab;
1382 
1383     /**
1384      * Splits the index file into multiple files, alphabetically, one file per letter, plus a file for any index
1385      * entries that start with non-alphabetical characters.
1386      * <br/>
1387      * <b>Note</b>: could be in conflict with &lt;noindex/&gt;.
1388      * <br/>
1389      * See
1390      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#splitindex">splitindex</a>.
1391      * <br/>
1392      */
1393     @Parameter( property = "splitindex", defaultValue = "false" )
1394     private boolean splitindex;
1395 
1396     /**
1397      * Specifies whether the stylesheet to be used is the <code>maven</code>'s javadoc stylesheet or
1398      * <code>java</code>'s default stylesheet when a <i>stylesheetfile</i> parameter is not specified.
1399      * <br/>
1400      * Possible values: <code>maven<code> or <code>java</code>.
1401      * <br/>
1402      */
1403     @Parameter( property = "stylesheet", defaultValue = "java" )
1404     private String stylesheet;
1405 
1406     /**
1407      * Specifies the path of an alternate HTML stylesheet file.
1408      * <br/>
1409      * The <code>stylesheetfile</code> could be an absolute File path.
1410      * <br/>
1411      * Since 2.6, it could be also be a path from a resource in the current project source directories
1412      * (i.e. <code>src/main/java</code>, <code>src/main/resources</code> or <code>src/main/javadoc</code>)
1413      * or from a resource in the Javadoc plugin dependencies, for instance:
1414      * <pre>
1415      * &lt;stylesheetfile&gt;path/to/your/resource/yourstylesheet.css&lt;/stylesheetfile&gt;
1416      * </pre>
1417      * Where <code>path/to/your/resource/yourstylesheet.css</code> could be in <code>src/main/javadoc</code>.
1418      * <pre>
1419      * &lt;build&gt;
1420      *   &lt;plugins&gt;
1421      *     &lt;plugin&gt;
1422      *       &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
1423      *       &lt;artifactId&gt;maven-javadoc-plugin&lt;/artifactId&gt;
1424      *       &lt;configuration&gt;
1425      *         &lt;stylesheetfile&gt;path/to/your/resource/yourstylesheet.css&lt;/stylesheetfile&gt;
1426      *         ...
1427      *       &lt;/configuration&gt;
1428      *       &lt;dependencies&gt;
1429      *         &lt;dependency&gt;
1430      *           &lt;groupId&gt;groupId&lt;/groupId&gt;
1431      *           &lt;artifactId&gt;artifactId&lt;/artifactId&gt;
1432      *           &lt;version&gt;version&lt;/version&gt;
1433      *         &lt;/dependency&gt;
1434      *       &lt;/dependencies&gt;
1435      *     &lt;/plugin&gt;
1436      *     ...
1437      *   &lt;plugins&gt;
1438      * &lt;/build&gt;
1439      * </pre>
1440      * Where <code>path/to/your/resource/yourstylesheet.css</code> is defined in the
1441      * <code>groupId:artifactId:version</code> javadoc plugin dependency.
1442      * <br/>
1443      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#stylesheetfile">
1444      * stylesheetfile</a>.
1445      */
1446     @Parameter( property = "stylesheetfile" )
1447     private String stylesheetfile;
1448 
1449     /**
1450      * Specifies the path of an additional HTML stylesheet file relative to the {@code javadocDirectory}
1451      * Example:
1452      * <pre>
1453      *     &lt;addStylesheets&gt;
1454      *         &lt;resources/addstylesheet.css&lt;/addStylesheet&gt;
1455      *     &lt;/addStylesheets&gt;
1456      * </pre>
1457      * @since 3.3.0
1458      */
1459     @Parameter
1460     private String[] addStylesheets;
1461 
1462     /**
1463      * Specifies the class file that starts the taglet used in generating the documentation for that tag.
1464      * <br/>
1465      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#taglet">taglet</a>.
1466      * <br/>
1467      * Since
1468      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java 1.4</a>.
1469      */
1470     @Parameter( property = "taglet" )
1471     private String taglet;
1472 
1473     /**
1474      * Specifies the Taglet artifact containing the taglet class files (.class).
1475      * <br/>
1476      * See
1477      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1478      * <br/>
1479      * Example:
1480      * <pre>
1481      * &lt;taglets&gt;
1482      *   &lt;taglet&gt;
1483      *     &lt;tagletClass&gt;com.sun.tools.doclets.ToDoTaglet&lt;/tagletClass&gt;
1484      *   &lt;/taglet&gt;
1485      *   &lt;taglet&gt;
1486      *     &lt;tagletClass&gt;package.to.AnotherTagletClass&lt;/tagletClass&gt;
1487      *   &lt;/taglet&gt;
1488      *   ...
1489      * &lt;/taglets&gt;
1490      * &lt;tagletArtifact&gt;
1491      *   &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1492      *   &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1493      *   &lt;version&gt;version-Taglet&lt;/version&gt;
1494      * &lt;/tagletArtifact&gt;
1495      * </pre>
1496      * <br/>
1497      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/TagletArtifact.html">Javadoc</a>.
1498      * <br/>
1499      *
1500      * @since 2.1
1501      */
1502     @Parameter( property = "tagletArtifact" )
1503     private TagletArtifact tagletArtifact;
1504 
1505     /**
1506      * Specifies several Taglet artifacts containing the taglet class files (.class). These taglets class names will be
1507      * auto-detect and so no need to specify them.
1508      * <br/>
1509      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#taglet">taglet</a>.
1510      * <br/>
1511      * See
1512      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1513      * <br/>
1514      * Example:
1515      * <pre>
1516      * &lt;tagletArtifacts&gt;
1517      *   &lt;tagletArtifact&gt;
1518      *     &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1519      *     &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1520      *     &lt;version&gt;version-Taglet&lt;/version&gt;
1521      *   &lt;/tagletArtifact&gt;
1522      *   ...
1523      * &lt;/tagletArtifacts&gt;
1524      * </pre>
1525      * <br/>
1526      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/TagletArtifact.html">Javadoc</a>.
1527      * <br/>
1528      *
1529      * @since 2.5
1530      */
1531     @Parameter( property = "tagletArtifacts" )
1532     private TagletArtifact[] tagletArtifacts;
1533 
1534     /**
1535      * Specifies the search paths for finding taglet class files (.class). The <code>tagletpath</code> can contain
1536      * multiple paths by separating them with a colon (<code>:</code>) or a semi-colon (<code>;</code>).
1537      * <br/>
1538      * See
1539      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1540      * <br/>
1541      * Since
1542      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java 1.4</a>.
1543      */
1544     @Parameter( property = "tagletpath" )
1545     private String tagletpath;
1546 
1547     /**
1548      * Enables the Javadoc tool to interpret multiple taglets.
1549      * <br/>
1550      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#taglet">taglet</a>.
1551      * <br/>
1552      * See
1553      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tagletpath">tagletpath</a>.
1554      * <br/>
1555      * Example:
1556      * <pre>
1557      * &lt;taglets&gt;
1558      *   &lt;taglet&gt;
1559      *     &lt;tagletClass&gt;com.sun.tools.doclets.ToDoTaglet&lt;/tagletClass&gt;
1560      *     &lt;!--&lt;tagletpath&gt;/home/taglets&lt;/tagletpath&gt;--&gt;
1561      *     &lt;tagletArtifact&gt;
1562      *       &lt;groupId&gt;group-Taglet&lt;/groupId&gt;
1563      *       &lt;artifactId&gt;artifact-Taglet&lt;/artifactId&gt;
1564      *       &lt;version&gt;version-Taglet&lt;/version&gt;
1565      *     &lt;/tagletArtifact&gt;
1566      *   &lt;/taglet&gt;
1567      * &lt;/taglets&gt;
1568      * </pre>
1569      * <br/>
1570      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/Taglet.html">Javadoc</a>.
1571      * <br/>
1572      *
1573      * @since 2.1
1574      */
1575     @Parameter( property = "taglets" )
1576     private Taglet[] taglets;
1577 
1578     /**
1579      * Enables the Javadoc tool to interpret a simple, one-argument custom block tag tagname in doc comments.
1580      * <br/>
1581      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#tag">tag</a>.
1582      * <br/>
1583      * Since
1584      * <a href="https://docs.oracle.com/javase/7/docs/technotes/guides/javadoc/whatsnew-1.4.html#summary">Java 1.4</a>.
1585      * <br/>
1586      * Example:
1587      * <pre>
1588      * &lt;tags&gt;
1589      *   &lt;tag&gt;
1590      *     &lt;name&gt;todo&lt;/name&gt;
1591      *     &lt;placement&gt;a&lt;/placement&gt;
1592      *     &lt;head&gt;To Do:&lt;/head&gt;
1593      *   &lt;/tag&gt;
1594      * &lt;/tags&gt;
1595      * </pre>
1596      * <b>Note</b>: the placement should be a combinaison of Xaoptcmf letters:
1597      * <ul>
1598      * <li><b><code>X</code></b> (disable tag)</li>
1599      * <li><b><code>a</code></b> (all)</li>
1600      * <li><b><code>o</code></b> (overview)</li>
1601      * <li><b><code>p</code></b> (packages)</li>
1602      * <li><b><code>t</code></b> (types, that is classes and interfaces)</li>
1603      * <li><b><code>c</code></b> (constructors)</li>
1604      * <li><b><code>m</code></b> (methods)</li>
1605      * <li><b><code>f</code></b> (fields)</li>
1606      * </ul>
1607      * See <a href="./apidocs/org/apache/maven/plugins/javadoc/options/Tag.html">Javadoc</a>.
1608      * <br/>
1609      */
1610     @Parameter( property = "tags" )
1611     private Tag[] tags;
1612 
1613     /**
1614      * Specifies the top text to be placed at the top of each output file.
1615      * <br/>
1616      * See <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6227616">6227616</a>.
1617      * <br/>
1618      * Since Java 6.0
1619      *
1620      * @since 2.4
1621      */
1622     @Parameter( property = "top" )
1623     private String top;
1624 
1625     /**
1626      * Includes one "Use" page for each documented class and package.
1627      * <br/>
1628      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#use">use</a>.
1629      * <br/>
1630      */
1631     @Parameter( property = "use", defaultValue = "true" )
1632     private boolean use;
1633 
1634     /**
1635      * Includes the version text in the generated docs.
1636      * <br/>
1637      * See <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#version">version</a>.
1638      * <br/>
1639      */
1640     @Parameter( property = "version", defaultValue = "true" )
1641     private boolean version;
1642 
1643     /**
1644      * Specifies the title to be placed in the HTML title tag.
1645      * <br/>
1646      * See
1647      * <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#windowtitle">windowtitle</a>.
1648      * <br/>
1649      */
1650     @Parameter( property = "windowtitle", defaultValue = "${project.name} ${project.version} API" )
1651     private String windowtitle;
1652 
1653     /**
1654      * Whether dependency -sources jars should be resolved and included as source paths for javadoc generation.
1655      * This is useful when creating javadocs for a distribution project.
1656      *
1657      * @since 2.7
1658      */
1659     @Parameter( defaultValue = "false" )
1660     private boolean includeDependencySources;
1661 
1662     /**
1663      * Directory where unpacked project sources / test-sources should be cached.
1664      *
1665      * @see #includeDependencySources
1666      * @since 2.7
1667      */
1668     @Parameter( defaultValue = "${project.build.directory}/distro-javadoc-sources" )
1669     private File sourceDependencyCacheDir;
1670 
1671     /**
1672      * Whether to include transitive dependencies in the list of dependency -sources jars to include in this javadoc
1673      * run.
1674      *
1675      * @see #includeDependencySources
1676      * @since 2.7
1677      * @deprecated if these sources depend on transitive dependencies, those dependencies should be added to the pom as
1678      *             direct dependencies
1679      */
1680     @Parameter( defaultValue = "false" )
1681     @Deprecated
1682     private boolean includeTransitiveDependencySources;
1683 
1684     /**
1685      * List of included dependency-source patterns. Example: <code>org.apache.maven:*</code>
1686      *
1687      * @see #includeDependencySources
1688      * @since 2.7
1689      */
1690     @Parameter
1691     private List<String> dependencySourceIncludes;
1692 
1693     /**
1694      * List of excluded dependency-source patterns. Example: <code>org.apache.maven.shared:*</code>
1695      *
1696      * @see #includeDependencySources
1697      * @since 2.7
1698      */
1699     @Parameter
1700     private List<String> dependencySourceExcludes;
1701 
1702     /**
1703      * Directory into which assembled {@link JavadocOptions} instances will be written before they
1704      * are added to javadoc resources bundles.
1705      *
1706      * @since 2.7
1707      */
1708     @Parameter( defaultValue = "${project.build.directory}/javadoc-bundle-options", readonly = true )
1709     private File javadocOptionsDir;
1710 
1711     /**
1712      * Transient variable to allow lazy-resolution of javadoc bundles from dependencies, so they can
1713      * be used at various points in the javadoc generation process.
1714      *
1715      * @since 2.7
1716      */
1717     private transient List<JavadocBundle> dependencyJavadocBundles;
1718 
1719     /**
1720      * Capability to add additional dependencies to the javadoc classpath.
1721      * Example:
1722      * <pre>
1723      * &lt;additionalDependencies&gt;
1724      *   &lt;additionalDependency&gt;
1725      *     &lt;groupId&gt;geronimo-spec&lt;/groupId&gt;
1726      *     &lt;artifactId&gt;geronimo-spec-jta&lt;/artifactId&gt;
1727      *     &lt;version&gt;1.0.1B-rc4&lt;/version&gt;
1728      *   &lt;/additionalDependency&gt;
1729      * &lt;/additionalDependencies&gt;
1730      * </pre>
1731      *
1732      * @since 2.8.1
1733      */
1734     @Parameter
1735     private List<AdditionalDependency> additionalDependencies;
1736 
1737     /**
1738      * Include filters on the source files. Default is **\/\*.java.
1739      * These are ignored if you specify subpackages or subpackage excludes.
1740      *
1741      * @since 2.9
1742      */
1743     @Parameter
1744     private List<String> sourceFileIncludes;
1745 
1746     /**
1747      * exclude filters on the source files.
1748      * These are ignored if you specify subpackages or subpackage excludes.
1749      *
1750      * @since 2.9
1751      */
1752     @Parameter
1753     private List<String> sourceFileExcludes;
1754 
1755     /**
1756      * To apply the security fix on generated javadoc see http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-1571
1757      * @since 2.9.1
1758      */
1759     @Parameter( defaultValue = "true", property = "maven.javadoc.applyJavadocSecurityFix" )
1760     private boolean applyJavadocSecurityFix = true;
1761 
1762     /**
1763      * <p>
1764      * Allow for configuration of the javadoc tool via maven toolchains.
1765      * This overrules the toolchain selected by the maven-toolchain-plugin.
1766      * </p>
1767      *
1768      * <p>Examples:</p>
1769      * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html">
1770      *     Guide to Toolchains</a> for more info)
1771      *
1772      * <pre>
1773      * {@code
1774      *    <configuration>
1775      *        ...
1776      *        <jdkToolchain>
1777      *            <version>11</version>
1778      *        </jdkToolchain>
1779      *    </configuration>
1780      *
1781      *    <configuration>
1782      *        ...
1783      *        <jdkToolchain>
1784      *            <version>1.8</version>
1785      *            <vendor>zulu</vendor>
1786      *        </jdkToolchain>
1787      *    </configuration>
1788      *    }
1789      * </pre>
1790      *
1791      * <strong>note:</strong> requires at least Maven 3.3.1
1792      *
1793      * @since 3.0.0
1794      */
1795     @Parameter
1796     private Map<String, String> jdkToolchain;
1797 
1798     /**
1799      * <p>
1800      * Location of the file used to store the state of the previous javadoc run.
1801      * This is used to skip the generation if nothing has changed.
1802      * </p>
1803      *
1804      * @since 3.2.0
1805      */
1806     @Parameter( property = "staleDataPath",
1807             defaultValue = "${project.build.directory}/maven-javadoc-plugin-stale-data.txt" )
1808     private File staleDataPath;
1809 
1810 
1811     /**
1812      * <p>
1813      * Comma separated list of modules (artifactId) to not add in aggregated javadoc
1814      * </p>
1815      *
1816      * @since 3.2.0
1817      */
1818     @Parameter( property = "maven.javadoc.skippedModules" )
1819     private String skippedModules;
1820 
1821     /**
1822      * Timestamp for reproducible output archive entries, either formatted as ISO 8601
1823      * <code>yyyy-MM-dd'T'HH:mm:ssXXX</code> or as an int representing seconds since the epoch (like
1824      * <a href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH</a>).
1825      *
1826      * @since 3.2.0
1827      */
1828     @Parameter( defaultValue = "${project.build.outputTimestamp}" )
1829     protected String outputTimestamp;
1830 
1831     // ----------------------------------------------------------------------
1832     // protected methods
1833     // ----------------------------------------------------------------------
1834 
1835     /**
1836      * Indicates whether this goal is flagged with <code>@aggregator</code>.
1837      *
1838      * @return <code>true</code> if the goal is designed as an aggregator, <code>false</code> otherwise.
1839      * @see AggregatorJavadocReport
1840      * @see AggregatorTestJavadocReport
1841      */
1842     protected boolean isAggregator()
1843     {
1844         return false;
1845     }
1846 
1847     /**
1848      * Indicates whether this goal generates documentation for the <code>Java Test code</code>.
1849      *
1850      * @return <code>true</code> if the goal generates Test Javadocs, <code>false</code> otherwise.
1851      */
1852     protected boolean isTest()
1853     {
1854         return false;
1855     }
1856 
1857     /**
1858      * @return the output directory
1859      */
1860     protected String getOutputDirectory()
1861     {
1862         return outputDirectory.getAbsoluteFile().toString();
1863     }
1864 
1865     protected MavenProject getProject()
1866     {
1867         return project;
1868     }
1869 
1870     /**
1871      * @param p not null maven project
1872      * @return the list of directories where compiled classes are placed for the given project. These dirs are
1873      *         added in the javadoc classpath.
1874      */
1875     protected List<File> getProjectBuildOutputDirs( MavenProject p )
1876     {
1877         if ( StringUtils.isEmpty( p.getBuild().getOutputDirectory() ) )
1878         {
1879             return Collections.emptyList();
1880         }
1881 
1882         return Collections.singletonList( new File( p.getBuild().getOutputDirectory() ) );
1883     }
1884 
1885     /**
1886      * Either returns the attached artifact file or outputDirectory
1887      *
1888      * @param project
1889      * @return
1890      */
1891     protected File getClassesFile( MavenProject project )
1892     {
1893         if ( !isAggregator() && isTest() )
1894         {
1895             return null;
1896         }
1897 
1898         if ( project.getArtifact() != null && project.getArtifact().getFile() != null )
1899         {
1900             File artifactFile = project.getArtifact().getFile();
1901             if ( artifactFile.isDirectory() || artifactFile.getName().endsWith( ".jar" ) )
1902             {
1903                 return artifactFile;
1904             }
1905         }
1906         else if ( project.getExecutionProject() != null
1907                         && project.getExecutionProject().getArtifact() != null
1908                         && project.getExecutionProject().getArtifact().getFile() != null )
1909         {
1910             File artifactFile = project.getExecutionProject().getArtifact().getFile();
1911             if ( artifactFile.isDirectory() || artifactFile.getName().endsWith( ".jar" ) )
1912             {
1913                 return artifactFile;
1914             }
1915         }
1916 
1917         if ( project.getBuild().getOutputDirectory() != null )
1918         {
1919             return new File( project.getBuild().getOutputDirectory() );
1920         }
1921         else
1922         {
1923             return null;
1924         }
1925     }
1926 
1927     /**
1928      * @param p not null maven project
1929      * @return the list of source paths for the given project
1930      */
1931     protected List<String> getProjectSourceRoots( MavenProject p )
1932     {
1933         if ( "pom".equals( p.getPackaging().toLowerCase() ) )
1934         {
1935             return Collections.emptyList();
1936         }
1937 
1938         return ( p.getCompileSourceRoots() == null
1939             ? Collections.<String>emptyList()
1940             : new LinkedList<>( p.getCompileSourceRoots() ) );
1941     }
1942 
1943     /**
1944      * @param p not null maven project
1945      * @return the list of source paths for the execution project of the given project
1946      */
1947     protected List<String> getExecutionProjectSourceRoots( MavenProject p )
1948     {
1949         if ( "pom".equals( p.getExecutionProject().getPackaging().toLowerCase() ) )
1950         {
1951             return Collections.emptyList();
1952         }
1953 
1954         return ( p.getExecutionProject().getCompileSourceRoots() == null
1955             ? Collections.<String>emptyList()
1956             : new LinkedList<>( p.getExecutionProject().getCompileSourceRoots() ) );
1957     }
1958 
1959     /**
1960      * @return the current javadoc directory
1961      */
1962     protected File getJavadocDirectory()
1963     {
1964         return javadocDirectory;
1965     }
1966 
1967     /**
1968      * @return the doclint specific checks configuration
1969      */
1970     protected String getDoclint()
1971     {
1972         return doclint;
1973     }
1974 
1975     /**
1976      * @return the title to be placed near the top of the overview summary file
1977      */
1978     protected String getDoctitle()
1979     {
1980         return doctitle;
1981     }
1982 
1983     /**
1984      * @return the overview documentation file from the user parameter or from the <code>javadocdirectory</code>
1985      */
1986     protected File getOverview()
1987     {
1988         return overview;
1989     }
1990 
1991     /**
1992      * @return the title to be placed in the HTML title tag
1993      */
1994     protected String getWindowtitle()
1995     {
1996         return windowtitle;
1997     }
1998 
1999     /**
2000      * @return the charset attribute or the value of {@link #getDocencoding()} if <code>null</code>.
2001      */
2002     private String getCharset()
2003     {
2004         return ( StringUtils.isEmpty( charset ) ) ? getDocencoding() : charset;
2005     }
2006 
2007     /**
2008      * @return the docencoding attribute or <code>UTF-8</code> if <code>null</code>.
2009      */
2010     private String getDocencoding()
2011     {
2012         return ( StringUtils.isEmpty( docencoding ) ) ? ReaderFactory.UTF_8 : docencoding;
2013     }
2014 
2015     /**
2016      * @return the encoding attribute or the value of <code>file.encoding</code> system property if <code>null</code>.
2017      */
2018     private String getEncoding()
2019     {
2020         return ( StringUtils.isEmpty( encoding ) ) ? ReaderFactory.FILE_ENCODING : encoding;
2021     }
2022 
2023     @Override
2024     public void execute()
2025         throws MojoExecutionException, MojoFailureException
2026     {
2027         verifyRemovedParameter( "aggregator" );
2028         verifyRemovedParameter( "proxyHost" );
2029         verifyRemovedParameter( "proxyPort" );
2030         verifyReplacedParameter( "additionalparam", "additionalOptions" );
2031 
2032         doExecute();
2033     }
2034 
2035     abstract void doExecute() throws MojoExecutionException, MojoFailureException;
2036 
2037     protected final void verifyRemovedParameter( String paramName )
2038     {
2039         Xpp3Dom configDom = mojo.getConfiguration();
2040         if ( configDom != null )
2041         {
2042             if ( configDom.getChild( paramName ) != null )
2043             {
2044                 throw new IllegalArgumentException( "parameter '" + paramName
2045                     + "' has been removed from the plugin, please verify documentation." );
2046             }
2047         }
2048     }
2049 
2050     private void verifyReplacedParameter( String oldParamName, String newParamNew )
2051     {
2052         Xpp3Dom configDom = mojo.getConfiguration();
2053         if ( configDom != null )
2054         {
2055             if ( configDom.getChild( oldParamName ) != null )
2056             {
2057                 throw new IllegalArgumentException( "parameter '" + oldParamName
2058                     + "' has been replaced with " + newParamNew + ", please verify documentation." );
2059             }
2060         }
2061     }
2062 
2063     /**
2064      * The <a href="package-summary.html">package documentation</a> details the
2065      * Javadoc Options used by this Plugin.
2066      *
2067      * @param unusedLocale the wanted locale (actually unused).
2068      * @throws MavenReportException if any
2069      */
2070     protected void executeReport( Locale unusedLocale )
2071         throws MavenReportException
2072     {
2073         if ( skip )
2074         {
2075             getLog().info( "Skipping javadoc generation" );
2076             return;
2077         }
2078 
2079         if ( getLog().isDebugEnabled() )
2080         {
2081             this.debug = true;
2082         }
2083 
2084         // NOTE: Always generate this file, to allow javadocs from modules to be aggregated via
2085         // useDependencySources in a distro module build.
2086         try
2087         {
2088             buildJavadocOptions();
2089         }
2090         catch ( IOException e )
2091         {
2092             throw new MavenReportException( "Failed to generate javadoc options file: " + e.getMessage(), e );
2093         }
2094 
2095         Collection<JavadocModule> sourcePaths = getSourcePaths();
2096 
2097         Collection<Path> collectedSourcePaths = sourcePaths.stream()
2098                                                            .flatMap( e -> e.getSourcePaths().stream() )
2099                                                            .collect( Collectors.toList() );
2100 
2101         Map<Path, Collection<String>> files = getFiles( collectedSourcePaths );
2102         if ( !canGenerateReport( files ) )
2103         {
2104             return;
2105         }
2106 
2107         // ----------------------------------------------------------------------
2108         // Find the javadoc executable and version
2109         // ----------------------------------------------------------------------
2110 
2111         String jExecutable;
2112         try
2113         {
2114             jExecutable = getJavadocExecutable();
2115         }
2116         catch ( IOException e )
2117         {
2118             throw new MavenReportException( "Unable to find javadoc command: " + e.getMessage(), e );
2119         }
2120         setFJavadocVersion( new File( jExecutable ) );
2121 
2122         Collection<String> packageNames;
2123         if ( javadocRuntimeVersion.isAtLeast( "9" ) )
2124         {
2125             packageNames = getPackageNamesRespectingJavaModules( sourcePaths );
2126         }
2127         else
2128         {
2129             packageNames = getPackageNames( files );
2130         }
2131 
2132         // ----------------------------------------------------------------------
2133         // Javadoc output directory as File
2134         // ----------------------------------------------------------------------
2135 
2136         File javadocOutputDirectory = new File( getOutputDirectory() );
2137         if ( javadocOutputDirectory.exists() && !javadocOutputDirectory.isDirectory() )
2138         {
2139             throw new MavenReportException( "IOException: " + getOutputDirectory() + " is not a directory." );
2140         }
2141         if ( javadocOutputDirectory.exists() && !javadocOutputDirectory.canWrite() )
2142         {
2143             throw new MavenReportException( "IOException: " + getOutputDirectory() + " is not writable." );
2144         }
2145         javadocOutputDirectory.mkdirs();
2146 
2147         // ----------------------------------------------------------------------
2148         // Copy all resources
2149         // ----------------------------------------------------------------------
2150 
2151         copyAllResources( javadocOutputDirectory );
2152 
2153         // ----------------------------------------------------------------------
2154         // Create command line for Javadoc
2155         // ----------------------------------------------------------------------
2156 
2157         Commandline cmd = new Commandline();
2158         cmd.getShell().setQuotedArgumentsEnabled( false ); // for Javadoc JVM args
2159         cmd.setWorkingDirectory( javadocOutputDirectory.getAbsolutePath() );
2160         cmd.setExecutable( jExecutable );
2161 
2162         // ----------------------------------------------------------------------
2163         // Wrap Javadoc JVM args
2164         // ----------------------------------------------------------------------
2165 
2166         addMemoryArg( cmd, "-Xmx", this.maxmemory );
2167         addMemoryArg( cmd, "-Xms", this.minmemory );
2168         addProxyArg( cmd );
2169 
2170         if ( StringUtils.isNotEmpty( additionalJOption ) )
2171         {
2172             cmd.createArg().setValue( additionalJOption );
2173         }
2174 
2175         if ( additionalJOptions != null && additionalJOptions.length != 0 )
2176         {
2177             for ( String jo : additionalJOptions )
2178             {
2179                 cmd.createArg().setValue( jo );
2180             }
2181         }
2182 
2183         // ----------------------------------------------------------------------
2184         // Wrap Standard doclet Options
2185         // ----------------------------------------------------------------------
2186         List<String> standardDocletArguments = new ArrayList<>();
2187 
2188         Set<OfflineLink> offlineLinks;
2189         if ( StringUtils.isEmpty( doclet ) || useStandardDocletOptions )
2190         {
2191             offlineLinks = getLinkofflines();
2192             addStandardDocletOptions( javadocOutputDirectory, standardDocletArguments, offlineLinks );
2193         }
2194         else
2195         {
2196             offlineLinks = Collections.emptySet();
2197         }
2198 
2199         // ----------------------------------------------------------------------
2200         // Wrap Javadoc options
2201         // ----------------------------------------------------------------------
2202         List<String> javadocArguments = new ArrayList<>();
2203 
2204         addJavadocOptions( javadocOutputDirectory, javadocArguments, sourcePaths, offlineLinks );
2205 
2206         // ----------------------------------------------------------------------
2207         // Write options file and include it in the command line
2208         // ----------------------------------------------------------------------
2209 
2210         List<String> arguments = new ArrayList<>( javadocArguments.size() + standardDocletArguments.size() );
2211         arguments.addAll( javadocArguments );
2212         arguments.addAll( standardDocletArguments );
2213 
2214         if ( arguments.size() > 0 )
2215         {
2216             addCommandLineOptions( cmd, arguments, javadocOutputDirectory );
2217         }
2218 
2219         // ----------------------------------------------------------------------
2220         // Write packages file and include it in the command line
2221         // ----------------------------------------------------------------------
2222 
2223         // MJAVADOC-365 if includes/excludes are specified, these take precedence over the default
2224         // package-based mode and force javadoc into file-based mode unless subpackages are
2225         // specified. Subpackages take precedence over file-based include/excludes. Why? Because
2226         // getFiles(...) returns an empty list when subpackages are specified.
2227         boolean includesExcludesActive =
2228             ( sourceFileIncludes != null && !sourceFileIncludes.isEmpty() )
2229                 || ( sourceFileExcludes != null && !sourceFileExcludes.isEmpty() );
2230         if ( includesExcludesActive && !StringUtils.isEmpty( subpackages ) )
2231         {
2232             getLog().warn( "sourceFileIncludes and sourceFileExcludes have no effect when subpackages are specified!" );
2233             includesExcludesActive = false;
2234         }
2235         if ( !packageNames.isEmpty() && !includesExcludesActive )
2236         {
2237             addCommandLinePackages( cmd, javadocOutputDirectory, packageNames );
2238 
2239             // ----------------------------------------------------------------------
2240             // Write argfile file and include it in the command line
2241             // ----------------------------------------------------------------------
2242 
2243             List<String> specialFiles = getSpecialFiles( files );
2244 
2245             if ( !specialFiles.isEmpty() )
2246             {
2247                 addCommandLineArgFile( cmd, javadocOutputDirectory, specialFiles );
2248             }
2249         }
2250         else
2251         {
2252             // ----------------------------------------------------------------------
2253             // Write argfile file and include it in the command line
2254             // ----------------------------------------------------------------------
2255 
2256             List<String> allFiles = new ArrayList<>();
2257             for ( Map.Entry<Path, Collection<String>> filesEntry : files.entrySet() )
2258             {
2259                 for ( String file : filesEntry.getValue() )
2260                 {
2261                     allFiles.add( filesEntry.getKey().resolve( file ).toString() );
2262                 }
2263             }
2264 
2265             if ( !files.isEmpty() )
2266             {
2267                 addCommandLineArgFile( cmd, javadocOutputDirectory, allFiles );
2268             }
2269         }
2270 
2271         // ----------------------------------------------------------------------
2272         // Execute command line
2273         // ----------------------------------------------------------------------
2274 
2275         executeJavadocCommandLine( cmd, javadocOutputDirectory );
2276 
2277         // delete generated javadoc files only if no error and no debug mode
2278         // [MJAVADOC-336] Use File.delete() instead of File.deleteOnExit() to
2279         // prevent these files from making their way into archives.
2280         if ( !debug )
2281         {
2282             for ( int i = 0; i < cmd.getArguments().length; i++ )
2283             {
2284                 String arg = cmd.getArguments()[i].trim();
2285 
2286                 if ( !arg.startsWith( "@" ) )
2287                 {
2288                     continue;
2289                 }
2290 
2291                 File argFile = new File( javadocOutputDirectory, arg.substring( 1 ) );
2292                 if ( argFile.exists() )
2293                 {
2294                     argFile.delete();
2295                 }
2296             }
2297 
2298             File scriptFile = new File( javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME );
2299             if ( scriptFile.exists() )
2300             {
2301                 scriptFile.delete();
2302             }
2303         }
2304         if ( applyJavadocSecurityFix )
2305         {
2306             // finally, patch the Javadoc vulnerability in older Javadoc tools (CVE-2013-1571):
2307             try
2308             {
2309                 final int patched = fixFrameInjectionBug( javadocOutputDirectory, getDocencoding() );
2310                 if ( patched > 0 )
2311                 {
2312                     getLog().info(
2313                         String.format( "Fixed Javadoc frame injection vulnerability (CVE-2013-1571) in %d files.",
2314                                        patched ) );
2315                 }
2316             }
2317             catch ( IOException e )
2318             {
2319                 throw new MavenReportException( "Failed to patch javadocs vulnerability: " + e.getMessage(), e );
2320             }
2321         }
2322         else
2323         {
2324           getLog().info( "applying javadoc security fix has been disabled" );
2325         }
2326     }
2327 
2328     /**
2329      * Method to get the files on the specified source paths
2330      *
2331      * @param sourcePaths a Collection that contains the paths to the source files
2332      * @return a List that contains the specific path for every source file
2333      * @throws MavenReportException {@link MavenReportException} issue while generating report
2334      */
2335     protected Map<Path, Collection<String>> getFiles( Collection<Path> sourcePaths )
2336         throws MavenReportException
2337     {
2338         Map<Path, Collection<String>> mappedFiles = new LinkedHashMap<>( sourcePaths.size() );
2339         if ( StringUtils.isEmpty( subpackages ) )
2340         {
2341             Collection<String> excludedPackages = getExcludedPackages();
2342 
2343             final boolean autoExclude;
2344             if ( release != null )
2345             {
2346                 autoExclude = JavaVersion.parse( release ).isBefore( "9" );
2347             }
2348             else if ( source != null )
2349             {
2350                 autoExclude = JavaVersion.parse( source ).isBefore( "9" );
2351             }
2352             else
2353             {
2354                 autoExclude = false;
2355             }
2356 
2357             for ( Path sourcePath : sourcePaths )
2358             {
2359                 File sourceDirectory = sourcePath.toFile();
2360                 List<String> files = new ArrayList<>( JavadocUtil.getFilesFromSource( sourceDirectory,
2361                         sourceFileIncludes, sourceFileExcludes,
2362                         excludedPackages ) );
2363 
2364                 if ( autoExclude && files.remove( "module-info.java" ) )
2365                 {
2366                     getLog().debug( "Auto exclude module-info.java due to source value" );
2367                 }
2368                 mappedFiles.put( sourcePath, files );
2369             }
2370         }
2371 
2372         return mappedFiles;
2373     }
2374 
2375     /**
2376      * Method to get the source paths per reactorProject. If no source path is specified in the parameter, the compile
2377      * source roots of the project will be used.
2378      *
2379      * @return a Map of the project absolute source paths per projects key (G:A)
2380      * @throws MavenReportException {@link MavenReportException} issue while generating report
2381      * @see JavadocUtil#pruneDirs(MavenProject, Collection)
2382      */
2383     protected Collection<JavadocModule> getSourcePaths()
2384         throws MavenReportException
2385     {
2386         Collection<JavadocModule> mappedSourcePaths = new ArrayList<>();
2387 
2388         if ( StringUtils.isEmpty( sourcepath ) )
2389         {
2390             if ( !"pom".equals( project.getPackaging()  ) )
2391             {
2392                 Set<Path> sourcePaths =
2393                     new LinkedHashSet<>( JavadocUtil.pruneDirs( project, getProjectSourceRoots( project ) ) );
2394 
2395                 if ( project.getExecutionProject() != null )
2396                 {
2397                     sourcePaths.addAll( JavadocUtil.pruneDirs( project, getExecutionProjectSourceRoots( project ) ) );
2398                 }
2399 
2400                 /*
2401                  * Should be after the source path (i.e. -sourcepath '.../src/main/java;.../src/main/javadoc') and *not*
2402                  * the opposite. If not, the javadoc tool always copies doc files, even if -docfilessubdirs is not
2403                  * setted.
2404                  */
2405                 if ( getJavadocDirectory() != null )
2406                 {
2407                     File javadocDir = getJavadocDirectory();
2408                     if ( javadocDir.exists() && javadocDir.isDirectory() )
2409                     {
2410                         Collection<Path> l =
2411                             JavadocUtil.pruneDirs( project,
2412                                                Collections.singletonList( getJavadocDirectory().getAbsolutePath() ) );
2413                         sourcePaths.addAll( l );
2414                     }
2415                 }
2416                 if ( !sourcePaths.isEmpty() )
2417                 {
2418                     mappedSourcePaths.add( buildJavadocModule( project, sourcePaths ) );
2419                 }
2420             }
2421 
2422             if ( isAggregator() )
2423             {
2424                 for ( MavenProject subProject : getAggregatedProjects() )
2425                 {
2426                     if ( subProject != project )
2427                     {
2428                         Collection<Path> additionalSourcePaths = new ArrayList<>();
2429 
2430                         List<String> sourceRoots = getProjectSourceRoots( subProject );
2431 
2432                         if ( subProject.getExecutionProject() != null )
2433                         {
2434                             sourceRoots.addAll( getExecutionProjectSourceRoots( subProject ) );
2435                         }
2436 
2437                         ArtifactHandler artifactHandler = subProject.getArtifact().getArtifactHandler();
2438                         if ( "java".equals( artifactHandler.getLanguage() ) )
2439                         {
2440                             additionalSourcePaths.addAll( JavadocUtil.pruneDirs( subProject, sourceRoots ) );
2441                         }
2442 
2443                         if ( getJavadocDirectory() != null )
2444                         {
2445                             String javadocDirRelative =
2446                                 PathUtils.toRelative( project.getBasedir(),
2447                                                       getJavadocDirectory().getAbsolutePath() );
2448                             File javadocDir = new File( subProject.getBasedir(), javadocDirRelative );
2449                             if ( javadocDir.exists() && javadocDir.isDirectory() )
2450                             {
2451                                 Collection<Path> l = JavadocUtil.pruneDirs( subProject, Collections.singletonList(
2452                                         javadocDir.getAbsolutePath() ) );
2453                                 additionalSourcePaths.addAll( l );
2454                             }
2455                         }
2456 
2457                         if ( !additionalSourcePaths.isEmpty() )
2458                         {
2459                             mappedSourcePaths.add( buildJavadocModule( subProject , additionalSourcePaths ) );
2460                         }
2461                     }
2462                 }
2463             }
2464 
2465             if ( includeDependencySources )
2466             {
2467                 mappedSourcePaths.addAll( getDependencySourcePaths() );
2468             }
2469         }
2470         else
2471         {
2472             Collection<Path> sourcePaths =
2473                 JavadocUtil.pruneDirs( project,
2474                                        new ArrayList<>( Arrays.asList( JavadocUtil.splitPath( sourcepath ) ) ) );
2475             if ( getJavadocDirectory() != null )
2476             {
2477                 Collection<Path> l = JavadocUtil.pruneDirs( project, Collections.singletonList(
2478                     getJavadocDirectory().getAbsolutePath() ) );
2479                 sourcePaths.addAll( l );
2480             }
2481 
2482             if ( !sourcePaths.isEmpty() )
2483             {
2484                 mappedSourcePaths.add( new JavadocModule( ArtifactUtils.versionlessKey( project.getGroupId(),
2485                                                                                         project.getArtifactId() ),
2486                                                           getClassesFile( project ),
2487                                                           sourcePaths ) );
2488             }
2489         }
2490 
2491         return mappedSourcePaths;
2492     }
2493 
2494     private JavadocModule buildJavadocModule( MavenProject project, Collection<Path> sourcePaths )
2495     {
2496         File classessFile = getClassesFile( project );
2497         ResolvePathResult resolvePathResult = getResolvePathResult( classessFile );
2498         if ( resolvePathResult == null )
2499         {
2500             return new JavadocModule( ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() ),
2501                                       classessFile,
2502                                       sourcePaths );
2503         }
2504         else
2505         {
2506             return new JavadocModule( ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() ),
2507                                       classessFile,
2508                                       sourcePaths,
2509                                       resolvePathResult.getModuleDescriptor(),
2510                                       resolvePathResult.getModuleNameSource() );
2511         }
2512     }
2513 
2514     /**
2515      * Recursively add the modules of the aggregatedProject to the set of aggregatedModules.
2516      *
2517      * @param aggregatedProject the project being aggregated
2518      * @param reactorProjectsMap map of (still) available reactor projects
2519      */
2520     private Set<MavenProject> modulesForAggregatedProject( MavenProject aggregatedProject,
2521                                                            Map<Path, MavenProject> reactorProjectsMap )
2522     {
2523         // Maven does not supply an easy way to get the projects representing
2524         // the modules of a project. So we will get the paths to the base
2525         // directories of the modules from the project and compare with the
2526         // base directories of the projects in the reactor.
2527 
2528         if ( aggregatedProject.getModules().isEmpty() )
2529         {
2530             return Collections.singleton( aggregatedProject );
2531         }
2532 
2533         Path basePath = aggregatedProject.getBasedir().toPath();
2534         List<Path> modulePaths = new LinkedList<>();
2535         for ( String module :  aggregatedProject.getModules() )
2536         {
2537             modulePaths.add( basePath.resolve( module ).normalize() );
2538         }
2539 
2540         Set<MavenProject> aggregatedModules = new LinkedHashSet<>();
2541 
2542         for ( Path modulePath : modulePaths )
2543         {
2544             MavenProject module = reactorProjectsMap.remove( modulePath );
2545             if ( module != null )
2546             {
2547                 aggregatedModules.addAll( modulesForAggregatedProject( module, reactorProjectsMap ) );
2548             }
2549         }
2550 
2551         return aggregatedModules;
2552     }
2553 
2554     /**
2555      * Override this method to customize the configuration for resolving dependency sources. The default
2556      * behavior enables the resolution of -sources jar files.
2557      * @param config {@link SourceResolverConfig}
2558      * @return {@link SourceResolverConfig}
2559      */
2560     protected SourceResolverConfig configureDependencySourceResolution( final SourceResolverConfig config )
2561     {
2562         return config.withCompileSources();
2563     }
2564 
2565     /**
2566      * Resolve dependency sources so they can be included directly in the javadoc process. To customize this,
2567      * override {@link AbstractJavadocMojo#configureDependencySourceResolution(SourceResolverConfig)}.
2568      * @return List of source paths.
2569      * @throws MavenReportException {@link MavenReportException}
2570      */
2571     protected final Collection<JavadocModule> getDependencySourcePaths()
2572         throws MavenReportException
2573     {
2574         try
2575         {
2576             if ( sourceDependencyCacheDir.exists() )
2577             {
2578                 FileUtils.forceDelete( sourceDependencyCacheDir );
2579                 sourceDependencyCacheDir.mkdirs();
2580             }
2581         }
2582         catch ( IOException e )
2583         {
2584             throw new MavenReportException(
2585                 "Failed to delete cache directory: " + sourceDependencyCacheDir + "\nReason: " + e.getMessage(), e );
2586         }
2587 
2588         final SourceResolverConfig config = getDependencySourceResolverConfig();
2589 
2590         try
2591         {
2592             return resourceResolver.resolveDependencySourcePaths( config );
2593         }
2594         catch ( final ArtifactResolutionException | ArtifactNotFoundException e )
2595         {
2596             throw new MavenReportException(
2597                 "Failed to resolve one or more javadoc source/resource artifacts:\n\n" + e.getMessage(), e );
2598         }
2599     }
2600 
2601     /**
2602      * Returns a ArtifactFilter that only includes direct dependencies of this project
2603      * (verified via groupId and artifactId).
2604      *
2605      * @return
2606      */
2607     private TransformableFilter createDependencyArtifactFilter()
2608     {
2609         Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
2610 
2611         List<String> artifactPatterns = new ArrayList<>( dependencyArtifacts.size() );
2612         for ( Artifact artifact : dependencyArtifacts )
2613         {
2614             artifactPatterns.add( artifact.getGroupId() + ":" + artifact.getArtifactId() );
2615         }
2616 
2617         return new PatternInclusionsFilter( artifactPatterns );
2618     }
2619 
2620     /**
2621      * Construct a SourceResolverConfig for resolving dependency sources and resources in a consistent
2622      * way, so it can be reused for both source and resource resolution.
2623      *
2624      * @since 2.7
2625      */
2626     private SourceResolverConfig getDependencySourceResolverConfig()
2627     {
2628         final List<TransformableFilter> andFilters = new ArrayList<>();
2629 
2630         final List<String> dependencyIncludes = dependencySourceIncludes;
2631         final List<String> dependencyExcludes = dependencySourceExcludes;
2632 
2633         if ( !includeTransitiveDependencySources || isNotEmpty( dependencyIncludes ) || isNotEmpty(
2634             dependencyExcludes ) )
2635         {
2636             if ( !includeTransitiveDependencySources )
2637             {
2638                 andFilters.add( createDependencyArtifactFilter() );
2639             }
2640 
2641             if ( isNotEmpty( dependencyIncludes ) )
2642             {
2643                 andFilters.add( new PatternInclusionsFilter( dependencyIncludes ) );
2644             }
2645 
2646             if ( isNotEmpty( dependencyExcludes ) )
2647             {
2648                 andFilters.add( new PatternExclusionsFilter( dependencyExcludes ) );
2649             }
2650         }
2651 
2652         return configureDependencySourceResolution( new SourceResolverConfig( project,
2653                                                   getProjectBuildingRequest( project ),
2654                                                   sourceDependencyCacheDir )
2655                                                   .withReactorProjects( this.reactorProjects ) )
2656                                                   .withFilter( new AndFilter( andFilters ) );
2657 
2658     }
2659 
2660     private ProjectBuildingRequest getProjectBuildingRequest( MavenProject currentProject )
2661     {
2662         return new DefaultProjectBuildingRequest( session.getProjectBuildingRequest() )
2663                           .setRemoteRepositories( currentProject.getRemoteArtifactRepositories() );
2664     }
2665 
2666     /**
2667      * Method that indicates whether the javadoc can be generated or not. If the project does not contain any source
2668      * files and no subpackages are specified, the plugin will terminate.
2669      *
2670      * @param files the project files
2671      * @return a boolean that indicates whether javadoc report can be generated or not
2672      */
2673     protected boolean canGenerateReport( Map<Path, Collection<String>> files )
2674     {
2675         for ( Collection<String> filesValues : files.values() )
2676         {
2677             if ( !filesValues.isEmpty() )
2678             {
2679                 return true;
2680             }
2681         }
2682 
2683         return !StringUtils.isEmpty( subpackages );
2684     }
2685 
2686     // ----------------------------------------------------------------------
2687     // private methods
2688     // ----------------------------------------------------------------------
2689 
2690     /**
2691      * Method to get the excluded source files from the javadoc and create the argument string
2692      * that will be included in the javadoc commandline execution.
2693      *
2694      * @param sourcePaths the collection of paths to the source files
2695      * @return a String that contains the exclude argument that will be used by javadoc
2696      * @throws MavenReportException
2697      */
2698     private String getExcludedPackages( Collection<Path> sourcePaths )
2699         throws MavenReportException
2700     {
2701         List<String> excludedNames = null;
2702 
2703         if ( StringUtils.isNotEmpty( sourcepath ) && StringUtils.isNotEmpty( subpackages ) )
2704         {
2705             Collection<String> excludedPackages = getExcludedPackages();
2706 
2707             excludedNames = JavadocUtil.getExcludedPackages( sourcePaths, excludedPackages );
2708         }
2709 
2710         String excludeArg = "";
2711         if ( StringUtils.isNotEmpty( subpackages ) && excludedNames != null )
2712         {
2713             // add the excludedpackage names
2714             excludeArg = StringUtils.join( excludedNames.iterator(), ":" );
2715         }
2716 
2717         return excludeArg;
2718     }
2719 
2720     /**
2721      * Method to format the specified source paths that will be accepted by the javadoc tool.
2722      *
2723      * @param sourcePaths the list of paths to the source files that will be included in the javadoc.
2724      * @return a String that contains the formatted source path argument, separated by the System pathSeparator
2725      *         string (colon (<code>:</code>) on Solaris or semi-colon (<code>;</code>) on Windows).
2726      * @see File#pathSeparator
2727      */
2728     private String getSourcePath( Collection<Path> sourcePaths )
2729     {
2730         String sourcePath = null;
2731 
2732         if ( StringUtils.isEmpty( subpackages ) || StringUtils.isNotEmpty( sourcepath ) )
2733         {
2734             sourcePath = StringUtils.join( sourcePaths.iterator(), File.pathSeparator );
2735         }
2736 
2737         return sourcePath;
2738     }
2739 
2740     /**
2741      * Method to get the packages specified in the <code>excludePackageNames</code> parameter. The packages are split
2742      * with ',', ':', or ';' and then formatted.
2743      *
2744      * @return an array of String objects that contain the package names
2745      * @throws MavenReportException
2746      */
2747     private Collection<String> getExcludedPackages()
2748         throws MavenReportException
2749     {
2750         Set<String> excluded = new LinkedHashSet<>();
2751 
2752         if ( includeDependencySources )
2753         {
2754             try
2755             {
2756                 resolveDependencyBundles();
2757             }
2758             catch ( IOException e )
2759             {
2760                 throw new MavenReportException(
2761                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
2762             }
2763 
2764             if ( isNotEmpty( dependencyJavadocBundles ) )
2765             {
2766                 for ( JavadocBundle bundle : dependencyJavadocBundles )
2767                 {
2768                     JavadocOptions options = bundle.getOptions();
2769                     if ( options != null && isNotEmpty( options.getExcludePackageNames() ) )
2770                     {
2771                         excluded.addAll( options.getExcludePackageNames() );
2772                     }
2773                 }
2774             }
2775         }
2776 
2777         // for the specified excludePackageNames
2778         if ( StringUtils.isNotEmpty( excludePackageNames ) )
2779         {
2780             List<String> packageNames = Arrays.asList( excludePackageNames.split( "[,:;]" ) );
2781             excluded.addAll( trimValues( packageNames ) );
2782         }
2783 
2784         return excluded;
2785     }
2786 
2787     private static List<String> trimValues( List<String> items )
2788     {
2789         List<String> result = new ArrayList<>( items.size() );
2790         for ( String item : items )
2791         {
2792             String trimmed = item.trim();
2793             if ( StringUtils.isEmpty( trimmed ) )
2794             {
2795                 continue;
2796             }
2797             result.add( trimmed );
2798         }
2799         return result;
2800     }
2801 
2802     /**
2803      * Method that gets the classpath and modulepath elements that will be specified in the javadoc
2804      * <code>-classpath</code> and <code>--module-path</code> parameter.
2805      * Since we have all the sources of the current reactor, it is sufficient to consider the
2806      * dependencies of the reactor modules, excluding the module artifacts which may not yet be available
2807      * when the reactor project is built for the first time.
2808      *
2809      * @return all classpath elements
2810      * @throws MavenReportException if any.
2811      */
2812     private Collection<File> getPathElements()
2813         throws MavenReportException
2814     {
2815         Set<File> classpathElements = new LinkedHashSet<>();
2816         Map<String, Artifact> compileArtifactMap = new LinkedHashMap<>();
2817 
2818         if ( isTest() )
2819         {
2820             classpathElements.addAll( getProjectBuildOutputDirs( project ) );
2821         }
2822 
2823         populateCompileArtifactMap( compileArtifactMap, project.getArtifacts() );
2824 
2825         if ( isAggregator() )
2826         {
2827             Collection<MavenProject> aggregatorProjects = getAggregatedProjects();
2828 
2829             List<String> reactorArtifacts = new ArrayList<>();
2830             for ( MavenProject p : aggregatorProjects )
2831             {
2832                 reactorArtifacts.add( p.getGroupId() + ':' + p.getArtifactId() );
2833             }
2834 
2835             TransformableFilter dependencyFilter = new AndFilter( Arrays.asList(
2836                                                                      new PatternExclusionsFilter( reactorArtifacts ),
2837                                                                      getDependencyScopeFilter() ) );
2838 
2839             for ( MavenProject subProject : aggregatorProjects )
2840             {
2841                 if ( subProject != project )
2842                 {
2843                     File projectArtifactFile = getClassesFile( subProject );
2844                     if ( projectArtifactFile != null )
2845                     {
2846                         classpathElements.add( projectArtifactFile );
2847                     }
2848                     else
2849                     {
2850                         classpathElements.addAll( getProjectBuildOutputDirs( subProject ) );
2851                     }
2852 
2853                     try
2854                     {
2855                         StringBuilder sb = new StringBuilder();
2856 
2857                         sb.append( "Compiled artifacts for " );
2858                         sb.append( subProject.getGroupId() ).append( ":" );
2859                         sb.append( subProject.getArtifactId() ).append( ":" );
2860                         sb.append( subProject.getVersion() ).append( '\n' );
2861 
2862                         ProjectBuildingRequest buildingRequest = getProjectBuildingRequest( subProject );
2863 
2864                         List<Dependency> managedDependencies = null;
2865                         if ( subProject.getDependencyManagement() != null )
2866                         {
2867                             managedDependencies = subProject.getDependencyManagement().getDependencies();
2868                         }
2869 
2870                         for ( ArtifactResult artifactResult
2871                                     : dependencyResolver.resolveDependencies( buildingRequest,
2872                                                                               subProject.getDependencies(),
2873                                                                               managedDependencies,
2874                                                                               dependencyFilter ) )
2875                         {
2876                             populateCompileArtifactMap( compileArtifactMap,
2877                                                         Collections.singletonList( artifactResult.getArtifact() ) );
2878 
2879                             sb.append( artifactResult.getArtifact().getFile() ).append( '\n' );
2880                         }
2881 
2882                         if ( getLog().isDebugEnabled() )
2883                         {
2884                             getLog().debug( sb.toString() );
2885                         }
2886 
2887                     }
2888                     catch ( DependencyResolverException e )
2889                     {
2890                         throw new MavenReportException( e.getMessage(), e );
2891                     }
2892                 }
2893             }
2894         }
2895 
2896         for ( Artifact a : compileArtifactMap.values() )
2897         {
2898             classpathElements.add( a.getFile() );
2899         }
2900 
2901         if ( additionalDependencies != null )
2902         {
2903             for ( Dependency dependency : additionalDependencies )
2904             {
2905                 Artifact artifact = resolveDependency( dependency );
2906                 getLog().debug( "add additional artifact with path " + artifact.getFile() );
2907                 classpathElements.add( artifact.getFile() );
2908             }
2909         }
2910 
2911         return classpathElements;
2912     }
2913 
2914     protected ScopeFilter getDependencyScopeFilter()
2915     {
2916         return ScopeFilter.including( Artifact.SCOPE_COMPILE, Artifact.SCOPE_PROVIDED, Artifact.SCOPE_SYSTEM );
2917     }
2918 
2919     /**
2920      * @param dependency {@link Dependency}
2921      * @return {@link Artifact}
2922      * @throws MavenReportException when artifact could not be resolved
2923      */
2924     public Artifact resolveDependency( Dependency dependency )
2925         throws MavenReportException
2926     {
2927         DefaultArtifactCoordinate coordinate = new DefaultArtifactCoordinate();
2928         coordinate.setGroupId( dependency.getGroupId() );
2929         coordinate.setArtifactId( dependency.getArtifactId() );
2930         coordinate.setVersion( dependency.getVersion() );
2931         coordinate.setClassifier( dependency.getClassifier() );
2932         coordinate.setExtension( artifactHandlerManager.getArtifactHandler( dependency.getType() ).getExtension() );
2933 
2934         try
2935         {
2936             return artifactResolver.resolveArtifact( getProjectBuildingRequest( project ), coordinate ).getArtifact();
2937         }
2938         catch ( ArtifactResolverException e )
2939         {
2940             throw new MavenReportException( "artifact resolver problem - " + e.getMessage(), e );
2941         }
2942     }
2943 
2944 
2945     //TODO remove the part with ToolchainManager lookup once we depend on
2946     //3.0.9 (have it as prerequisite). Define as regular component field then.
2947     protected final Toolchain getToolchain()
2948     {
2949         Toolchain tc = null;
2950 
2951         if ( jdkToolchain != null )
2952         {
2953             // Maven 3.3.1 has plugin execution scoped Toolchain Support
2954             try
2955             {
2956                 Method getToolchainsMethod =
2957                     toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
2958                                                            Map.class );
2959 
2960                 @SuppressWarnings( "unchecked" )
2961                 List<Toolchain> tcs =
2962                     (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
2963                                                                   jdkToolchain );
2964 
2965                 if ( tcs != null && tcs.size() > 0 )
2966                 {
2967                     tc = tcs.get( 0 );
2968                 }
2969             }
2970             catch ( SecurityException | ReflectiveOperationException e )
2971             {
2972                 // ignore
2973             }
2974         }
2975 
2976         if ( tc == null )
2977         {
2978             tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
2979         }
2980 
2981         return tc;
2982     }
2983 
2984     /**
2985      * Method to put the artifacts in the hashmap.
2986      *
2987      * @param compileArtifactMap the hashmap that will contain the artifacts
2988      * @param artifactList       the list of artifacts that will be put in the map
2989      * @throws MavenReportException if any
2990      */
2991     private void populateCompileArtifactMap( Map<String, Artifact> compileArtifactMap,
2992                                              Collection<Artifact> artifactList )
2993         throws MavenReportException
2994     {
2995         if ( artifactList == null )
2996         {
2997             return;
2998         }
2999 
3000         for ( Artifact newArtifact : artifactList )
3001         {
3002             File file = newArtifact.getFile();
3003 
3004             if ( file == null )
3005             {
3006                 throw new MavenReportException(
3007                     "Error in plugin descriptor - " + "dependency was not resolved for artifact: "
3008                         + newArtifact.getGroupId() + ":" + newArtifact.getArtifactId() + ":"
3009                         + newArtifact.getVersion() );
3010             }
3011 
3012             if ( compileArtifactMap.get( newArtifact.getDependencyConflictId() ) != null )
3013             {
3014                 Artifact oldArtifact = compileArtifactMap.get( newArtifact.getDependencyConflictId() );
3015 
3016                 ArtifactVersion oldVersion = new DefaultArtifactVersion( oldArtifact.getVersion() );
3017                 ArtifactVersion newVersion = new DefaultArtifactVersion( newArtifact.getVersion() );
3018                 if ( newVersion.compareTo( oldVersion ) > 0 )
3019                 {
3020                     compileArtifactMap.put( newArtifact.getDependencyConflictId(), newArtifact );
3021                 }
3022             }
3023             else
3024             {
3025                 compileArtifactMap.put( newArtifact.getDependencyConflictId(), newArtifact );
3026             }
3027         }
3028     }
3029 
3030     /**
3031      * Method that sets the bottom text that will be displayed on the bottom of the
3032      * javadocs.
3033      *
3034      * @return a String that contains the text that will be displayed at the bottom of the javadoc
3035      */
3036     private String getBottomText()
3037     {
3038         final String inceptionYear = project.getInceptionYear();
3039 
3040         // get Reproducible Builds outputTimestamp value
3041         final Calendar currentYearCal = Calendar.getInstance();
3042         final Date reproducibleDate = new MavenArchiver().parseOutputTimestamp( outputTimestamp );
3043         if ( reproducibleDate != null )
3044         {
3045             currentYearCal.setTime( reproducibleDate );
3046         }
3047         final String currentYear = String.valueOf( currentYearCal.get( Calendar.YEAR ) );
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         addArgIf( arguments, notimestamp, "-notimestamp", SINCE_JAVADOC_1_5 );
5660 
5661         addArgIf( arguments, notree, "-notree" );
5662 
5663         addArgIfNotEmpty( arguments, "-packagesheader", JavadocUtil.quotedArgument( packagesheader ),
5664                           SINCE_JAVADOC_1_4_2 );
5665 
5666         if ( !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) ) // Sun bug: 4714350
5667         {
5668             addArgIf( arguments, quiet, "-quiet", SINCE_JAVADOC_1_4 );
5669         }
5670 
5671         addArgIf( arguments, serialwarn, "-serialwarn" );
5672 
5673         addArgIf( arguments, splitindex, "-splitindex" );
5674 
5675         Optional<File> stylesheetfile = getStylesheetFile( javadocOutputDirectory );
5676 
5677         if ( stylesheetfile.isPresent() )
5678         {
5679             addArgIfNotEmpty( arguments, "-stylesheetfile",
5680                               JavadocUtil.quotedPathArgument( stylesheetfile.get().getAbsolutePath() ) );
5681         }
5682 
5683         addAddStyleSheets( arguments );
5684 
5685         if ( StringUtils.isNotEmpty( sourcepath ) && !isJavaDocVersionAtLeast( SINCE_JAVADOC_1_5 ) )
5686         {
5687             addArgIfNotEmpty( arguments, "-subpackages", subpackages, SINCE_JAVADOC_1_4 );
5688         }
5689 
5690         addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( taglet ), SINCE_JAVADOC_1_4 );
5691         addTaglets( arguments );
5692         addTagletsFromTagletArtifacts( arguments );
5693         addArgIfNotEmpty( arguments, "-tagletpath", JavadocUtil.quotedPathArgument( getTagletPath() ),
5694                           SINCE_JAVADOC_1_4 );
5695 
5696         addTags( arguments );
5697 
5698         addArgIfNotEmpty( arguments, "-top", JavadocUtil.quotedArgument( top ), false, false, SINCE_JAVADOC_1_6 );
5699 
5700         addArgIf( arguments, use, "-use" );
5701 
5702         addArgIf( arguments, version, "-version" );
5703 
5704         addArgIfNotEmpty( arguments, "-windowtitle", JavadocUtil.quotedArgument( getWindowtitle() ), false, false );
5705     }
5706 
5707     /**
5708      * Add <code>groups</code> parameter to arguments.
5709      *
5710      * @param arguments not null
5711      * @throws MavenReportException
5712      */
5713     private void addGroups( List<String> arguments )
5714         throws MavenReportException
5715     {
5716         Set<Group> groups = collectGroups();
5717         if ( isEmpty( groups ) )
5718         {
5719             return;
5720         }
5721 
5722         for ( Group group : groups )
5723         {
5724             if ( group == null || StringUtils.isEmpty( group.getTitle() ) || StringUtils.isEmpty(
5725                 group.getPackages() ) )
5726             {
5727                 if ( getLog().isWarnEnabled() )
5728                 {
5729                     getLog().warn( "A group option is empty. Ignore this option." );
5730                 }
5731             }
5732             else
5733             {
5734                 String groupTitle = StringUtils.replace( group.getTitle(), ",", "&#44;" );
5735                 addArgIfNotEmpty( arguments, "-group",
5736                                   JavadocUtil.quotedArgument( groupTitle ) + " " + JavadocUtil.quotedArgument(
5737                                       group.getPackages() ), true );
5738             }
5739         }
5740     }
5741 
5742     /**
5743      * Add <code>tags</code> parameter to arguments.
5744      *
5745      * @param arguments not null
5746      * @throws MavenReportException
5747      */
5748     private void addTags( List<String> arguments )
5749         throws MavenReportException
5750     {
5751         final String lineSeparator;
5752         if ( javadocRuntimeVersion.isBefore( "9" ) )
5753         {
5754             lineSeparator = " ";
5755         }
5756         else
5757         {
5758             lineSeparator = " \\\\" + SystemUtils.LINE_SEPARATOR;
5759         }
5760 
5761         for ( Tag tag : collectTags() )
5762         {
5763             if ( StringUtils.isEmpty( tag.getName() ) )
5764             {
5765                 if ( getLog().isWarnEnabled() )
5766                 {
5767                     getLog().warn( "A tag name is empty. Ignore this option." );
5768                 }
5769             }
5770             else
5771             {
5772                 String value = "\"" + tag.getName();
5773                 if ( StringUtils.isNotEmpty( tag.getPlacement() ) )
5774                 {
5775                     value += ":" + tag.getPlacement().replaceAll( "\\R", lineSeparator );
5776                     if ( StringUtils.isNotEmpty( tag.getHead() ) )
5777                     {
5778                         value += ":" + tag.getHead().replaceAll( "\\R", lineSeparator );
5779                     }
5780                 }
5781                 value += "\"";
5782                 addArgIfNotEmpty( arguments, "-tag", value, SINCE_JAVADOC_1_4 );
5783             }
5784         }
5785     }
5786 
5787     /**
5788      * Add <code>taglets</code> parameter to arguments.
5789      *
5790      * @param arguments not null
5791      */
5792     private void addTaglets( List<String> arguments )
5793     {
5794         if ( taglets == null )
5795         {
5796             return;
5797         }
5798 
5799         for ( Taglet taglet1 : taglets )
5800         {
5801             if ( ( taglet1 == null ) || ( StringUtils.isEmpty( taglet1.getTagletClass() ) ) )
5802             {
5803                 if ( getLog().isWarnEnabled() )
5804                 {
5805                     getLog().warn( "A taglet option is empty. Ignore this option." );
5806                 }
5807             }
5808             else
5809             {
5810                 addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( taglet1.getTagletClass() ),
5811                                   SINCE_JAVADOC_1_4 );
5812             }
5813         }
5814     }
5815 
5816     /**
5817      * Auto-detect taglets class name from <code>tagletArtifacts</code> and add them to arguments.
5818      *
5819      * @param arguments not null
5820      * @throws MavenReportException if any
5821      * @see JavadocUtil#getTagletClassNames(File)
5822      */
5823     private void addTagletsFromTagletArtifacts( List<String> arguments )
5824         throws MavenReportException
5825     {
5826         Set<TagletArtifact> tArtifacts = new LinkedHashSet<>();
5827         if ( tagletArtifacts != null && tagletArtifacts.length > 0 )
5828         {
5829             tArtifacts.addAll( Arrays.asList( tagletArtifacts ) );
5830         }
5831 
5832         if ( includeDependencySources )
5833         {
5834             try
5835             {
5836                 resolveDependencyBundles();
5837             }
5838             catch ( IOException e )
5839             {
5840                 throw new MavenReportException(
5841                     "Failed to resolve javadoc bundles from dependencies: " + e.getMessage(), e );
5842             }
5843 
5844             if ( isNotEmpty( dependencyJavadocBundles ) )
5845             {
5846                 for ( JavadocBundle bundle : dependencyJavadocBundles )
5847                 {
5848                     JavadocOptions options = bundle.getOptions();
5849                     if ( options != null && isNotEmpty( options.getTagletArtifacts() ) )
5850                     {
5851                         tArtifacts.addAll( options.getTagletArtifacts() );
5852                     }
5853                 }
5854             }
5855         }
5856 
5857         if ( isEmpty( tArtifacts ) )
5858         {
5859             return;
5860         }
5861 
5862         List<String> tagletsPath = new ArrayList<>();
5863 
5864         for ( TagletArtifact aTagletArtifact : tArtifacts )
5865         {
5866             if ( ( StringUtils.isNotEmpty( aTagletArtifact.getGroupId() ) ) && ( StringUtils.isNotEmpty(
5867                 aTagletArtifact.getArtifactId() ) ) && ( StringUtils.isNotEmpty( aTagletArtifact.getVersion() ) ) )
5868             {
5869                 Artifact artifact;
5870                 try
5871                 {
5872                     artifact = createAndResolveArtifact( aTagletArtifact );
5873                 }
5874                 catch ( ArtifactResolverException e )
5875                 {
5876                     throw new MavenReportException( "Unable to resolve artifact:" + aTagletArtifact, e );
5877                 }
5878 
5879                 tagletsPath.add( artifact.getFile().getAbsolutePath() );
5880             }
5881         }
5882 
5883         tagletsPath = JavadocUtil.pruneFiles( tagletsPath );
5884 
5885         for ( String tagletJar : tagletsPath )
5886         {
5887             if ( !tagletJar.toLowerCase( Locale.ENGLISH ).endsWith( ".jar" ) )
5888             {
5889                 continue;
5890             }
5891 
5892             List<String> tagletClasses;
5893             try
5894             {
5895                 tagletClasses = JavadocUtil.getTagletClassNames( new File( tagletJar ) );
5896             }
5897             catch ( IOException e )
5898             {
5899                 if ( getLog().isWarnEnabled() )
5900                 {
5901                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5902                                        + "'. Try to specify them with <taglets/>." );
5903                 }
5904                 if ( getLog().isDebugEnabled() )
5905                 {
5906                     getLog().debug( "IOException: " + e.getMessage(), e );
5907                 }
5908                 continue;
5909             }
5910             catch ( ClassNotFoundException e )
5911             {
5912                 if ( getLog().isWarnEnabled() )
5913                 {
5914                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5915                                        + "'. Try to specify them with <taglets/>." );
5916                 }
5917                 if ( getLog().isDebugEnabled() )
5918                 {
5919                     getLog().debug( "ClassNotFoundException: " + e.getMessage(), e );
5920                 }
5921                 continue;
5922             }
5923             catch ( NoClassDefFoundError e )
5924             {
5925                 if ( getLog().isWarnEnabled() )
5926                 {
5927                     getLog().warn( "Unable to auto-detect Taglet class names from '" + tagletJar
5928                                        + "'. Try to specify them with <taglets/>." );
5929                 }
5930                 if ( getLog().isDebugEnabled() )
5931                 {
5932                     getLog().debug( "NoClassDefFoundError: " + e.getMessage(), e );
5933                 }
5934                 continue;
5935             }
5936 
5937             if ( tagletClasses != null && !tagletClasses.isEmpty() )
5938             {
5939                 for ( String tagletClass : tagletClasses )
5940                 {
5941                     addArgIfNotEmpty( arguments, "-taglet", JavadocUtil.quotedArgument( tagletClass ),
5942                                       SINCE_JAVADOC_1_4 );
5943                 }
5944             }
5945         }
5946     }
5947 
5948     /**
5949      * Execute the Javadoc command line
5950      *
5951      * @param cmd                    not null
5952      * @param javadocOutputDirectory not null
5953      * @throws MavenReportException if any errors occur
5954      */
5955     private void executeJavadocCommandLine( Commandline cmd, File javadocOutputDirectory )
5956             throws MavenReportException
5957     {
5958         if ( staleDataPath != null )
5959         {
5960             if ( !isUpToDate( cmd ) )
5961             {
5962                 doExecuteJavadocCommandLine( cmd, javadocOutputDirectory );
5963                 StaleHelper.writeStaleData( cmd, staleDataPath.toPath() );
5964             }
5965         }
5966         else
5967         {
5968             doExecuteJavadocCommandLine( cmd, javadocOutputDirectory );
5969         }
5970     }
5971 
5972     /**
5973      * Check if the javadoc is uptodate or not
5974      *
5975      * @param cmd                    not null
5976      * @return <code>true</code> is the javadoc is uptodate, <code>false</code> otherwise
5977      * @throws MavenReportException  if any error occur
5978      */
5979     private boolean isUpToDate( Commandline cmd )
5980             throws MavenReportException
5981     {
5982         try
5983         {
5984             String curdata = StaleHelper.getStaleData( cmd );
5985             Path cacheData = staleDataPath.toPath();
5986             String prvdata;
5987             if ( Files.isRegularFile( cacheData ) )
5988             {
5989                 prvdata = new String( Files.readAllBytes( cacheData ), StandardCharsets.UTF_8 );
5990             }
5991             else
5992             {
5993                 prvdata = null;
5994             }
5995             if ( curdata.equals( prvdata ) )
5996             {
5997                 getLog().info( "Skipping javadoc generation, everything is up to date." );
5998                 return true;
5999             }
6000             else
6001             {
6002                 if ( prvdata == null )
6003                 {
6004                     getLog().info( "No previous run data found, generating javadoc." );
6005                 }
6006                 else
6007                 {
6008                     getLog().info( "Configuration changed, re-generating javadoc." );
6009                 }
6010             }
6011         }
6012         catch ( IOException e )
6013         {
6014             throw new MavenReportException( "Error checking uptodate status", e );
6015         }
6016         return false;
6017     }
6018 
6019     /**
6020      * Execute the Javadoc command line
6021      *
6022      * @param cmd                    not null
6023      * @param javadocOutputDirectory not null
6024      * @throws MavenReportException if any errors occur
6025      */
6026     private void doExecuteJavadocCommandLine( Commandline cmd, File javadocOutputDirectory )
6027         throws MavenReportException
6028     {
6029         if ( getLog().isDebugEnabled() )
6030         {
6031             // no quoted arguments
6032             getLog().debug( CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" ) );
6033         }
6034 
6035         String cmdLine = null;
6036         if ( debug )
6037         {
6038             cmdLine = CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" );
6039 
6040             writeDebugJavadocScript( cmdLine, javadocOutputDirectory );
6041         }
6042 
6043         CommandLineUtils.StringStreamConsumer err = new JavadocUtil.JavadocOutputStreamConsumer();
6044         CommandLineUtils.StringStreamConsumer out = new JavadocUtil.JavadocOutputStreamConsumer();
6045         try
6046         {
6047             int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
6048 
6049             String output = ( StringUtils.isEmpty( out.getOutput() ) ? null : '\n' + out.getOutput().trim() );
6050 
6051             if ( exitCode != 0 )
6052             {
6053                 if ( cmdLine == null )
6054                 {
6055                     cmdLine = CommandLineUtils.toString( cmd.getCommandline() ).replaceAll( "'", "" );
6056                 }
6057                 writeDebugJavadocScript( cmdLine, javadocOutputDirectory );
6058 
6059                 if ( StringUtils.isNotEmpty( output ) && StringUtils.isEmpty( err.getOutput() )
6060                     && isJavadocVMInitError( output ) )
6061                 {
6062                     throw new MavenReportException( output + '\n' + '\n' + JavadocUtil.ERROR_INIT_VM + '\n'
6063                         + "Or, try to reduce the Java heap size for the Javadoc goal using "
6064                         + "-Dminmemory=<size> and -Dmaxmemory=<size>." + '\n' + '\n' + "Command line was: " + cmdLine
6065                         + '\n' + '\n' + "Refer to the generated Javadoc files in '" + javadocOutputDirectory
6066                         + "' dir.\n" );
6067                 }
6068 
6069                 if ( StringUtils.isNotEmpty( output ) )
6070                 {
6071                     getLog().info( output );
6072                 }
6073 
6074                 StringBuilder msg = new StringBuilder( "\nExit code: " );
6075                 msg.append( exitCode );
6076                 if ( StringUtils.isNotEmpty( err.getOutput() ) )
6077                 {
6078                     msg.append( " - " ).append( err.getOutput() );
6079                 }
6080                 msg.append( '\n' );
6081                 msg.append( "Command line was: " ).append( cmdLine ).append( '\n' ).append( '\n' );
6082 
6083                 msg.append( "Refer to the generated Javadoc files in '" ).append( javadocOutputDirectory )
6084                 .append( "' dir.\n" );
6085 
6086                 throw new MavenReportException( msg.toString() );
6087             }
6088 
6089             if ( StringUtils.isNotEmpty( output ) )
6090             {
6091                 getLog().info( output );
6092             }
6093         }
6094         catch ( CommandLineException e )
6095         {
6096             throw new MavenReportException( "Unable to execute javadoc command: " + e.getMessage(), e );
6097         }
6098 
6099         // ----------------------------------------------------------------------
6100         // Handle Javadoc warnings
6101         // ----------------------------------------------------------------------
6102 
6103         if ( containsWarnings( err.getOutput() ) )
6104         {
6105             if ( getLog().isWarnEnabled() )
6106             {
6107                 getLog().warn( "Javadoc Warnings" );
6108 
6109                 StringTokenizer token = new StringTokenizer( err.getOutput(), "\n" );
6110                 while ( token.hasMoreTokens() )
6111                 {
6112                     String current = token.nextToken().trim();
6113 
6114                     getLog().warn( current );
6115                 }
6116             }
6117 
6118             if ( failOnWarnings )
6119             {
6120                 throw new MavenReportException( "Project contains Javadoc Warnings" );
6121             }
6122         }
6123     }
6124 
6125     private boolean containsWarnings( String output )
6126     {
6127         // JDK-8268774 / JDK-8270831
6128         if ( this.javadocRuntimeVersion.isBefore( "17" ) )
6129         {
6130             return StringUtils.isNotEmpty( output );
6131         }
6132         else
6133         {
6134             return Arrays.stream( output.split( "\\R" ) )
6135                             .reduce( ( first, second ) -> second ) // last line
6136                             .filter( line -> line.matches( "\\d+ warnings?" ) )
6137                             .isPresent();
6138         }
6139     }
6140 
6141     /**
6142      * Patches the given Javadoc output directory to work around CVE-2013-1571
6143      * (see http://www.kb.cert.org/vuls/id/225657).
6144      *
6145      * @param javadocOutputDirectory directory to scan for vulnerabilities
6146      * @param outputEncoding         encoding used by the javadoc tool (-docencoding parameter).
6147      *                               If {@code null}, the platform's default encoding is used (like javadoc does).
6148      * @return the number of patched files
6149      */
6150     private int fixFrameInjectionBug( File javadocOutputDirectory, String outputEncoding )
6151         throws IOException
6152     {
6153         final String fixData;
6154 
6155         try ( InputStream in = this.getClass().getResourceAsStream( "frame-injection-fix.txt" ) )
6156         {
6157             if ( in == null )
6158             {
6159                 throw new FileNotFoundException( "Missing resource 'frame-injection-fix.txt' in classpath." );
6160             }
6161             fixData = org.codehaus.plexus.util.StringUtils
6162                 .unifyLineSeparators( IOUtil.toString( in, "US-ASCII" ) ).trim();
6163         }
6164 
6165         final DirectoryScanner ds = new DirectoryScanner();
6166         ds.setBasedir( javadocOutputDirectory );
6167         ds.setCaseSensitive( false );
6168         ds.setIncludes( new String[]{ "**/index.html", "**/index.htm", "**/toc.html", "**/toc.htm" } );
6169         ds.addDefaultExcludes();
6170         ds.scan();
6171         int patched = 0;
6172         for ( String f : ds.getIncludedFiles() )
6173         {
6174             final File file = new File( javadocOutputDirectory, f );
6175             // we load the whole file as one String (toc/index files are
6176             // generally small, because they only contain frameset declaration):
6177             final String fileContents = FileUtils.fileRead( file, outputEncoding );
6178             // check if file may be vulnerable because it was not patched with "validURL(url)":
6179             if ( !StringUtils.contains( fileContents, "function validURL(url) {" ) )
6180             {
6181                 // we need to patch the file!
6182                 final String patchedFileContents =
6183                     StringUtils.replaceOnce( fileContents, "function loadFrames() {", fixData );
6184                 if ( !patchedFileContents.equals( fileContents ) )
6185                 {
6186                     FileUtils.fileWrite( file, outputEncoding, patchedFileContents );
6187                     patched++;
6188                 }
6189             }
6190         }
6191         return patched;
6192     }
6193 
6194     /**
6195      * @param outputFile        not nul
6196      * @param inputResourceName a not null resource in <code>src/main/java</code>, <code>src/main/resources</code> or
6197      *                          <code>src/main/javadoc</code> or in the Javadoc plugin dependencies.
6198      * @return the resource file absolute path as String
6199      * @since 2.6
6200      */
6201     private Optional<File> getResource( File outputFile, String inputResourceName )
6202     {
6203         if ( inputResourceName.startsWith( "/" ) )
6204         {
6205             inputResourceName = inputResourceName.replaceFirst( "//*", "" );
6206         }
6207 
6208         List<String> classPath = new ArrayList<>();
6209         classPath.add( project.getBuild().getSourceDirectory() );
6210 
6211         URL resourceURL = getResource( classPath, inputResourceName );
6212         if ( resourceURL != null )
6213         {
6214             getLog().debug( inputResourceName + " found in the main src directory of the project." );
6215             return Optional.of( FileUtils.toFile( resourceURL ) );
6216         }
6217 
6218         classPath.clear();
6219         List<Resource> resources = project.getBuild().getResources();
6220         for ( Resource resource : resources )
6221         {
6222             classPath.add( resource.getDirectory() );
6223         }
6224         resourceURL = getResource( classPath, inputResourceName );
6225         if ( resourceURL != null )
6226         {
6227             getLog().debug( inputResourceName + " found in the main resources directories of the project." );
6228             return Optional.of( FileUtils.toFile( resourceURL ) );
6229         }
6230 
6231         if ( javadocDirectory.exists() )
6232         {
6233             classPath.clear();
6234             classPath.add( javadocDirectory.getAbsolutePath() );
6235             resourceURL = getResource( classPath, inputResourceName );
6236             if ( resourceURL != null )
6237             {
6238                 getLog().debug( inputResourceName + " found in the main javadoc directory of the project." );
6239                 return Optional.of( FileUtils.toFile( resourceURL ) );
6240             }
6241         }
6242 
6243         classPath.clear();
6244         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
6245         Plugin javadocPlugin = getPlugin( project, pluginId );
6246         if ( javadocPlugin != null && javadocPlugin.getDependencies() != null )
6247         {
6248             List<Dependency> dependencies = javadocPlugin.getDependencies();
6249             for ( Dependency dependency : dependencies )
6250             {
6251                 ResourcesArtifact resourceArtifact = new ResourcesArtifact();
6252                 resourceArtifact.setGroupId( dependency.getGroupId() );
6253                 resourceArtifact.setArtifactId( dependency.getArtifactId() );
6254                 resourceArtifact.setVersion( dependency.getVersion() );
6255                 resourceArtifact.setClassifier( dependency.getClassifier() );
6256                 Artifact artifact = null;
6257                 try
6258                 {
6259                     artifact = createAndResolveArtifact( resourceArtifact );
6260                 }
6261                 catch ( Exception e )
6262                 {
6263                     logError( "Unable to retrieve the dependency: " + dependency + ". Ignored.", e );
6264                 }
6265 
6266                 if ( artifact != null && artifact.getFile().exists() )
6267                 {
6268                     classPath.add( artifact.getFile().getAbsolutePath() );
6269                 }
6270             }
6271             resourceURL = getResource( classPath, inputResourceName );
6272             if ( resourceURL != null )
6273             {
6274                 getLog().debug( inputResourceName + " found in javadoc plugin dependencies." );
6275                 try
6276                 {
6277                     JavadocUtil.copyResource( resourceURL, outputFile );
6278 
6279                     return Optional.of( outputFile );
6280                 }
6281                 catch ( IOException e )
6282                 {
6283                     logError( "IOException: " + e.getMessage(), e );
6284                 }
6285             }
6286         }
6287 
6288         getLog().warn( "Unable to find the resource '" + inputResourceName + "'. Using default Javadoc resources." );
6289 
6290         return Optional.empty();
6291     }
6292 
6293     /**
6294      * @param classPath a not null String list of files where resource will be looked up
6295      * @param resource a not null resource to find in the class path
6296      * @return the resource from the given classpath or null if not found
6297      * @see ClassLoader#getResource(String)
6298      * @since 2.6
6299      */
6300     private URL getResource( final List<String> classPath, final String resource )
6301     {
6302         List<URL> urls = new ArrayList<>( classPath.size() );
6303         for ( String filename : classPath )
6304         {
6305             try
6306             {
6307                 urls.add( new File( filename ).toURI().toURL() );
6308             }
6309             catch ( MalformedURLException e )
6310             {
6311                 getLog().error( "MalformedURLException: " + e.getMessage() );
6312             }
6313         }
6314 
6315         URLClassLoader javadocClassLoader = new URLClassLoader( urls.toArray( new URL[urls.size()] ), null );
6316         try
6317         {
6318             return javadocClassLoader.getResource( resource );
6319         }
6320         finally
6321         {
6322             try
6323             {
6324                 javadocClassLoader.close();
6325             }
6326             catch ( IOException ex )
6327             {
6328                 // ignore
6329             }
6330         }
6331     }
6332 
6333     /**
6334      * Get the full javadoc goal. Loads the plugin's pom.properties to get the current plugin version.
6335      *
6336      * @return <code>org.apache.maven.plugins:maven-javadoc-plugin:CURRENT_VERSION:[test-]javadoc</code>
6337      */
6338     private String getFullJavadocGoal()
6339     {
6340         String javadocPluginVersion = null;
6341         String resource = "META-INF/maven/org.apache.maven.plugins/maven-javadoc-plugin/pom.properties";
6342         try ( InputStream resourceAsStream
6343                         = AbstractJavadocMojo.class.getClassLoader().getResourceAsStream( resource ) )
6344         {
6345             if ( resourceAsStream != null )
6346             {
6347                 Properties properties = new Properties();
6348                 properties.load( resourceAsStream );
6349                 if ( StringUtils.isNotEmpty( properties.getProperty( "version" ) ) )
6350                 {
6351                     javadocPluginVersion = properties.getProperty( "version" );
6352                 }
6353             }
6354         }
6355         catch ( IOException e )
6356         {
6357             // nop
6358         }
6359 
6360         StringBuilder sb = new StringBuilder();
6361 
6362         sb.append( "org.apache.maven.plugins:maven-javadoc-plugin:" );
6363         if ( StringUtils.isNotEmpty( javadocPluginVersion ) )
6364         {
6365             sb.append( javadocPluginVersion ).append( ":" );
6366         }
6367 
6368         if ( this instanceof TestJavadocReport )
6369         {
6370             sb.append( "test-javadoc" );
6371         }
6372         else
6373         {
6374             sb.append( "javadoc" );
6375         }
6376 
6377         return sb.toString();
6378     }
6379 
6380     /**
6381      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
6382      *
6383      * @return the detected Javadoc links using the Maven conventions for all modules defined in the current project
6384      *         or an empty list
6385      * @throws MavenReportException if any
6386      * @see #detectOfflineLinks
6387      * @see #reactorProjects
6388      * @since 2.6
6389      */
6390     private List<OfflineLink> getModulesLinks()
6391         throws MavenReportException
6392     {
6393         List<MavenProject> aggregatedProjects = reactorProjects;
6394         if ( !detectOfflineLinks || isAggregator() || aggregatedProjects.isEmpty() )
6395         {
6396             return Collections.emptyList();
6397         }
6398 
6399         getLog().debug( "Trying to add links for modules..." );
6400 
6401         Set<String> dependencyArtifactIds = new HashSet<>();
6402         final Set<Artifact> dependencyArtifacts = project.getDependencyArtifacts();
6403         for ( Artifact artifact : dependencyArtifacts )
6404         {
6405             dependencyArtifactIds.add( artifact.getId() );
6406         }
6407 
6408         List<OfflineLink> modulesLinks = new ArrayList<>();
6409         String javadocDirRelative = PathUtils.toRelative( project.getBasedir(), getOutputDirectory() );
6410         for ( MavenProject p : aggregatedProjects )
6411         {
6412             if ( !dependencyArtifactIds.contains( p.getArtifact().getId() ) || ( p.getUrl() == null ) )
6413             {
6414                 continue;
6415             }
6416 
6417             File location = new File( p.getBasedir(), javadocDirRelative );
6418 
6419             if ( !location.exists() )
6420             {
6421                 if ( getLog().isDebugEnabled() )
6422                 {
6423                     getLog().debug( "Javadoc directory not found: " + location );
6424                 }
6425 
6426                 String javadocGoal = getFullJavadocGoal();
6427                 getLog().info(
6428                     "The goal '" + javadocGoal + "' has not been previously called for the module: '" + p.getId()
6429                         + "'. Trying to invoke it..." );
6430 
6431                 File invokerDir = new File( project.getBuild().getDirectory(), "invoker" );
6432                 invokerDir.mkdirs();
6433                 File invokerLogFile = FileUtils.createTempFile( "maven-javadoc-plugin", ".txt", invokerDir );
6434                 try
6435                 {
6436                     JavadocUtil.invokeMaven( getLog(), new File( localRepository.getBasedir() ), p.getFile(),
6437                                              Collections.singletonList( javadocGoal ), null, invokerLogFile,
6438                                              session.getRequest().getGlobalSettingsFile() );
6439                 }
6440                 catch ( MavenInvocationException e )
6441                 {
6442                     logError( "MavenInvocationException: " + e.getMessage(), e );
6443 
6444                     String invokerLogContent = JavadocUtil.readFile( invokerLogFile, null /* platform encoding */ );
6445 
6446                     // TODO: Why are we only interested in cases where the JVM won't start?
6447                     // [MJAVADOC-275][jdcasey] I changed the logic here to only throw an error WHEN
6448                     //   the JVM won't start (opposite of what it was).
6449                     if ( invokerLogContent != null && invokerLogContent.contains( JavadocUtil.ERROR_INIT_VM ) )
6450                     {
6451                         throw new MavenReportException( e.getMessage(), e );
6452                     }
6453                 }
6454                 finally
6455                 {
6456                     // just create the directory to prevent repeated invocations..
6457                     if ( !location.exists() )
6458                     {
6459                         getLog().warn( "Creating fake javadoc directory to prevent repeated invocations: " + location );
6460                         location.mkdirs();
6461                     }
6462                 }
6463             }
6464 
6465             if ( location.exists() )
6466             {
6467                 String url = getJavadocLink( p );
6468 
6469                 OfflineLink ol = new OfflineLink();
6470                 ol.setUrl( url );
6471                 ol.setLocation( location.getAbsolutePath() );
6472 
6473                 if ( getLog().isDebugEnabled() )
6474                 {
6475                     getLog().debug( "Added Javadoc offline link: " + url + " for the module: " + p.getId() );
6476                 }
6477 
6478                 modulesLinks.add( ol );
6479             }
6480         }
6481 
6482         return modulesLinks;
6483     }
6484 
6485     /**
6486      * Using Maven, a Javadoc link is given by <code>${project.url}/apidocs</code>.
6487      *
6488      * @return the detected Javadoc links using the Maven conventions for all dependencies defined in the current
6489      *         project or an empty list.
6490      * @see #detectLinks
6491      * @see #isValidJavadocLink(String, boolean)
6492      * @since 2.6
6493      */
6494     private List<String> getDependenciesLinks()
6495     {
6496         if ( !detectLinks )
6497         {
6498             return Collections.emptyList();
6499         }
6500 
6501         getLog().debug( "Trying to add links for dependencies..." );
6502 
6503         List<String> dependenciesLinks = new ArrayList<>();
6504 
6505         final Set<Artifact> dependencies = project.getDependencyArtifacts();
6506         for ( Artifact artifact : dependencies )
6507         {
6508             if ( artifact.getFile() == null || !artifact.getFile().exists() )
6509             {
6510                 continue;
6511             }
6512 
6513             Optional<DependencyLink> depLink =
6514                 this.dependencyLinks.stream().filter( d -> matches( d, artifact ) ).findAny();
6515 
6516             final String url;
6517             final boolean detected;
6518             if ( depLink.isPresent() )
6519             {
6520                 url = depLink.get().getUrl();
6521                 detected = false;
6522             }
6523             else
6524             {
6525                 try
6526                 {
6527                     MavenProject artifactProject =
6528                         mavenProjectBuilder.build( artifact, getProjectBuildingRequest( project ) ).getProject();
6529 
6530                     url = getJavadocLink( artifactProject );
6531                     detected = true;
6532                 }
6533                 catch ( ProjectBuildingException e )
6534                 {
6535                     logError( "ProjectBuildingException for " + artifact.toString() + ": " + e.getMessage(), e );
6536                     continue;
6537                 }
6538             }
6539 
6540             if ( url != null && isValidJavadocLink( url, detected ) )
6541             {
6542                 getLog().debug( "Added Javadoc link: " + url + " for " + artifact.getId() );
6543 
6544                 dependenciesLinks.add( url );
6545             }
6546         }
6547 
6548         return dependenciesLinks;
6549     }
6550 
6551     private boolean matches( DependencyLink d, Artifact artifact )
6552     {
6553         if ( d.getGroupId() != null && !d.getGroupId().equals( artifact.getGroupId() ) )
6554         {
6555             return false;
6556         }
6557         if ( d.getArtifactId() != null && !d.getArtifactId().equals( artifact.getArtifactId() ) )
6558         {
6559             return false;
6560         }
6561         if ( d.getClassifier() != null && !d.getClassifier().equals( artifact.getClassifier() ) )
6562         {
6563             return false;
6564         }
6565         return true;
6566     }
6567 
6568     /**
6569      * @return if {@code detectJavaApiLink}, the Java API link based on the {@code javaApiLinks} properties and the
6570      *         value of the <code>source</code> parameter in the
6571      *         <code>org.apache.maven.plugins:maven-compiler-plugin</code>
6572      *         defined in <code>${project.build.plugins}</code> or in <code>${project.build.pluginManagement}</code>,
6573      *         or the {@code javadocRuntimeVersion}, or <code>null</code> if not defined.
6574      * @see <a href="http://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source">source parameter</a>
6575      * @since 2.6
6576      */
6577     protected final OfflineLink getDefaultJavadocApiLink()
6578     {
6579         if ( !detectJavaApiLink )
6580         {
6581             return null;
6582         }
6583 
6584         final JavaVersion javaApiversion;
6585         if ( release != null )
6586         {
6587             javaApiversion = JavaVersion.parse( release );
6588         }
6589         else if ( source != null && !source.isEmpty() )
6590         {
6591             javaApiversion = JavaVersion.parse( source );
6592         }
6593         else
6594         {
6595             final String pluginId = "org.apache.maven.plugins:maven-compiler-plugin";
6596             String sourceConfigured = getPluginParameter( project, pluginId, "source" );
6597             if ( sourceConfigured != null )
6598             {
6599                 javaApiversion = JavaVersion.parse( sourceConfigured );
6600             }
6601             else
6602             {
6603                 getLog().debug( "No maven-compiler-plugin defined in ${build.plugins} or in "
6604                                     + "${project.build.pluginManagement} for the " + project.getId()
6605                                     + ". Added Javadoc API link according the javadoc executable version i.e.: "
6606                                     + javadocRuntimeVersion );
6607 
6608                 javaApiversion = javadocRuntimeVersion;
6609             }
6610         }
6611 
6612         final String javaApiKey;
6613         if ( javaApiversion.asMajor().isAtLeast( "9" ) )
6614         {
6615             javaApiKey = "api_" + javaApiversion.asMajor();
6616         }
6617         else
6618         {
6619             javaApiKey = "api_1." + javaApiversion.asMajor().toString().charAt( 0 );
6620         }
6621 
6622         final String javaApiLink;
6623         if ( javaApiLinks != null && javaApiLinks.containsKey( javaApiKey ) )
6624         {
6625             javaApiLink = javaApiLinks.getProperty( javaApiKey );
6626         }
6627         else if ( javaApiversion.isAtLeast( "16" ) )
6628         {
6629             javaApiLink = null; // JDK-8216497
6630         }
6631         else if ( javaApiversion.isAtLeast( "11" ) )
6632         {
6633             javaApiLink =
6634                 String.format( "https://docs.oracle.com/en/java/javase/%s/docs/api/", javaApiversion.getValue( 1 ) );
6635         }
6636         else if ( javaApiversion.asMajor().isAtLeast( "6" ) )
6637         {
6638             javaApiLink =
6639                 String.format( "https://docs.oracle.com/javase/%s/docs/api/", javaApiversion.asMajor().getValue( 1 ) );
6640         }
6641         else if ( javaApiversion.isAtLeast( "1.5" ) )
6642         {
6643             javaApiLink = "https://docs.oracle.com/javase/1.5.0/docs/api/";
6644         }
6645         else
6646         {
6647             javaApiLink = null;
6648         }
6649 
6650         if ( getLog().isDebugEnabled() )
6651         {
6652             if ( javaApiLink != null )
6653             {
6654                 getLog().debug( "Found Java API link: " + javaApiLink );
6655             }
6656             else
6657             {
6658                 getLog().debug( "No Java API link found." );
6659             }
6660         }
6661 
6662         if ( javaApiLink == null )
6663         {
6664             return null;
6665         }
6666 
6667         final Path javaApiListFile;
6668         final String resourceName;
6669         if ( javaApiversion.isAtLeast( "10" ) )
6670         {
6671             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "element-list" );
6672             resourceName = "java-api-element-list-" + javaApiversion.toString().substring( 0, 2 );
6673         }
6674         else if ( javaApiversion.asMajor().isAtLeast( "9" ) )
6675         {
6676             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "package-list" );
6677             resourceName = "java-api-package-list-9";
6678         }
6679         else
6680         {
6681             javaApiListFile = getJavadocOptionsFile().getParentFile().toPath().resolve( "package-list" );
6682             resourceName = "java-api-package-list-1." + javaApiversion.asMajor().toString().charAt( 0 );
6683         }
6684 
6685         OfflineLink link = new OfflineLink();
6686         link.setLocation( javaApiListFile.getParent().toAbsolutePath().toString() );
6687         link.setUrl( javaApiLink );
6688 
6689         InputStream in = this.getClass().getResourceAsStream( resourceName );
6690         if ( in != null )
6691         {
6692             try ( InputStream closableIS = in )
6693             {
6694                 // TODO only copy when changed
6695                 Files.copy( closableIS, javaApiListFile, StandardCopyOption.REPLACE_EXISTING );
6696             }
6697             catch ( IOException ioe )
6698             {
6699                 logError( "Can't get " + resourceName + ": " + ioe.getMessage(), ioe );
6700                 return null;
6701             }
6702         }
6703 
6704         return link;
6705     }
6706 
6707     /**
6708      * Follows all of the given links, and returns their last redirect locations. Ordering is kept.
6709      * This is necessary because javadoc tool doesn't follow links, see JDK-8190312 (MJAVADOC-427, MJAVADOC-487)
6710      *
6711      * @param links Links to follow.
6712      * @return Last redirect location of all the links.
6713      */
6714     private Set<String> followLinks( Set<String> links )
6715     {
6716         Set<String> redirectLinks = new LinkedHashSet<>( links.size() );
6717         for ( String link : links )
6718         {
6719             try
6720             {
6721                 redirectLinks.add( JavadocUtil.getRedirectUrl( new URI( link ).toURL(), settings ).toString() );
6722             }
6723             catch ( Exception e )
6724             {
6725                 // only print in debug, it should have been logged already in warn/error because link isn't valid
6726                 getLog().debug( "Could not follow " + link + ". Reason: " + e.getMessage() );
6727 
6728                 // Even when link produces error it should be kept in the set because the error might be caused by
6729                 // incomplete redirect configuration on the server side.
6730                 // This partially restores the previous behaviour before fix for MJAVADOC-427
6731                 redirectLinks.add( link );
6732             }
6733         }
6734         return redirectLinks;
6735     }
6736 
6737     /**
6738      * @param link not null
6739      * @param detecting <code>true</code> if the link is generated by
6740      * <code>detectLinks</code>, or <code>false</code> otherwise
6741      * @return <code>true</code> if the link has a <code>/package-list</code>, <code>false</code> otherwise.
6742      * @see <a href="https://docs.oracle.com/javase/7/docs/technotes/tools/solaris/javadoc.html#package-list">
6743      *      package-list spec</a>
6744      * @since 2.6
6745      */
6746     protected boolean isValidJavadocLink( String link, boolean detecting )
6747     {
6748         try
6749         {
6750             final URI packageListUri;
6751             final URI elementListUri;
6752 
6753             if ( link.trim().toLowerCase( Locale.ENGLISH ).startsWith( "http:" ) || link.trim().toLowerCase(
6754                 Locale.ENGLISH ).startsWith( "https:" ) || link.trim().toLowerCase( Locale.ENGLISH ).startsWith(
6755                 "ftp:" ) || link.trim().toLowerCase( Locale.ENGLISH ).startsWith( "file:" ) )
6756             {
6757                 packageListUri = new URI( link + '/' + PACKAGE_LIST );
6758                 elementListUri = new URI( link + '/' + ELEMENT_LIST );
6759             }
6760             else
6761             {
6762                 // links can be relative paths or files
6763                 File dir = new File( link );
6764                 if ( !dir.isAbsolute() )
6765                 {
6766                     dir = new File( getOutputDirectory(), link );
6767                 }
6768                 if ( !dir.isDirectory() )
6769                 {
6770                     if ( detecting )
6771                     {
6772                         getLog().warn( "The given File link: " + dir + " is not a dir." );
6773                     }
6774                     else
6775                     {
6776                         getLog().error( "The given File link: " + dir + " is not a dir." );
6777                     }
6778                 }
6779                 packageListUri = new File( dir, PACKAGE_LIST ).toURI();
6780                 elementListUri = new File( dir, ELEMENT_LIST ).toURI();
6781             }
6782 
6783 
6784             try
6785             {
6786                 if ( JavadocUtil.isValidElementList( elementListUri.toURL(), settings, validateLinks ) )
6787                 {
6788                     return true;
6789                 }
6790             }
6791             catch ( IOException e )
6792             {
6793             }
6794 
6795             if ( JavadocUtil.isValidPackageList( packageListUri.toURL(), settings, validateLinks ) )
6796             {
6797                 return true;
6798             }
6799 
6800             if ( getLog().isErrorEnabled() )
6801             {
6802                 if ( detecting )
6803                 {
6804                     getLog().warn( "Invalid links: "
6805                                     + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST + ". Ignored it." );
6806                 }
6807                 else
6808                 {
6809                     getLog().error( "Invalid links: "
6810                                     + link + " with /" + PACKAGE_LIST + " or / " + ELEMENT_LIST + ". Ignored it." );
6811                 }
6812             }
6813 
6814             return false;
6815         }
6816         catch ( URISyntaxException e )
6817         {
6818             if ( getLog().isErrorEnabled() )
6819             {
6820                 if ( detecting )
6821                 {
6822                     getLog().warn( "Malformed link: " + e.getInput() + ". Ignored it." );
6823                 }
6824                 else
6825                 {
6826                     getLog().error( "Malformed link: " + e.getInput() + ". Ignored it." );
6827                 }
6828             }
6829             return false;
6830         }
6831         catch ( IOException e )
6832         {
6833             if ( getLog().isErrorEnabled() )
6834             {
6835                 if ( detecting )
6836                 {
6837                     getLog().warn( "Error fetching link: " + link + ". Ignored it." );
6838                 }
6839                 else
6840                 {
6841                     getLog().error( "Error fetching link: " + link + ". Ignored it." );
6842                 }
6843             }
6844             return false;
6845         }
6846     }
6847 
6848     /**
6849      * Write a debug javadoc script in case of command line error or in debug mode.
6850      *
6851      * @param cmdLine                the current command line as string, not null.
6852      * @param javadocOutputDirectory the output dir, not null.
6853      * @see #executeJavadocCommandLine(Commandline, File)
6854      * @since 2.6
6855      */
6856     private void writeDebugJavadocScript( String cmdLine, File javadocOutputDirectory )
6857     {
6858         File commandLineFile = new File( javadocOutputDirectory, DEBUG_JAVADOC_SCRIPT_NAME );
6859         commandLineFile.getParentFile().mkdirs();
6860 
6861         try
6862         {
6863             FileUtils.fileWrite( commandLineFile.getAbsolutePath(), null /* platform encoding */, cmdLine );
6864 
6865             if ( !SystemUtils.IS_OS_WINDOWS )
6866             {
6867                 Runtime.getRuntime().exec( new String[]{ "chmod", "a+x", commandLineFile.getAbsolutePath() } );
6868             }
6869         }
6870         catch ( IOException e )
6871         {
6872             logError( "Unable to write '" + commandLineFile.getName() + "' debug script file", e );
6873         }
6874     }
6875 
6876     /**
6877      * Check if the Javadoc JVM is correctly started or not.
6878      *
6879      * @param output the command line output, not null.
6880      * @return <code>true</code> if Javadoc output command line contains Javadoc word, <code>false</code> otherwise.
6881      * @see #executeJavadocCommandLine(Commandline, File)
6882      * @since 2.6.1
6883      */
6884     private boolean isJavadocVMInitError( String output )
6885     {
6886         /*
6887          * see main.usage and main.Building_tree keys from
6888          * com.sun.tools.javadoc.resources.javadoc bundle in tools.jar
6889          */
6890         return !( output.contains( "Javadoc" ) || output.contains( "javadoc" ) );
6891     }
6892 
6893     // ----------------------------------------------------------------------
6894     // Static methods
6895     // ----------------------------------------------------------------------
6896 
6897     /**
6898      * @param p not null
6899      * @return the javadoc link based on the project url i.e. <code>${project.url}/${destDir}</code> where
6900      *         <code>destDir</code> is configued in the Javadoc plugin configuration (<code>apidocs</code> by default).
6901      * @since 2.6
6902      */
6903     private static String getJavadocLink( MavenProject p )
6904     {
6905         if ( p.getUrl() == null )
6906         {
6907             return null;
6908         }
6909 
6910         String url = cleanUrl( p.getUrl() );
6911         String destDir = "apidocs"; // see JavadocReport#destDir
6912 
6913         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
6914         String destDirConfigured = getPluginParameter( p, pluginId, "destDir" );
6915         if ( destDirConfigured != null )
6916         {
6917             destDir = destDirConfigured;
6918         }
6919 
6920         return url + "/" + destDir;
6921     }
6922 
6923     /**
6924      * @param url could be null.
6925      * @return the url cleaned or empty if url was null.
6926      * @since 2.6
6927      */
6928     private static String cleanUrl( String url )
6929     {
6930         if ( url == null )
6931         {
6932             return "";
6933         }
6934 
6935         url = url.trim();
6936         while ( url.endsWith( "/" ) )
6937         {
6938             url = url.substring( 0, url.lastIndexOf( "/" ) );
6939         }
6940 
6941         return url;
6942     }
6943 
6944     /**
6945      * @param p        not null
6946      * @param pluginId not null key of the plugin defined in {@link org.apache.maven.model.Build#getPluginsAsMap()}
6947      *                 or in {@link org.apache.maven.model.PluginManagement#getPluginsAsMap()}
6948      * @return the Maven plugin defined in <code>${project.build.plugins}</code> or in
6949      *         <code>${project.build.pluginManagement}</code>, or <code>null</code> if not defined.
6950      * @since 2.6
6951      */
6952     private static Plugin getPlugin( MavenProject p, String pluginId )
6953     {
6954         if ( ( p.getBuild() == null ) || ( p.getBuild().getPluginsAsMap() == null ) )
6955         {
6956             return null;
6957         }
6958 
6959         Plugin plugin = p.getBuild().getPluginsAsMap().get( pluginId );
6960 
6961         if ( ( plugin == null ) && ( p.getBuild().getPluginManagement() != null ) && (
6962             p.getBuild().getPluginManagement().getPluginsAsMap() != null ) )
6963         {
6964             plugin = p.getBuild().getPluginManagement().getPluginsAsMap().get( pluginId );
6965         }
6966 
6967         return plugin;
6968     }
6969 
6970     /**
6971      * @param p        not null
6972      * @param pluginId not null
6973      * @param param    not null
6974      * @return the simple parameter as String defined in the plugin configuration by <code>param</code> key
6975      *         or <code>null</code> if not found.
6976      * @since 2.6
6977      */
6978     private static String getPluginParameter( MavenProject p, String pluginId, String param )
6979     {
6980 //        p.getGoalConfiguration( pluginGroupId, pluginArtifactId, executionId, goalId );
6981         Plugin plugin = getPlugin( p, pluginId );
6982         if ( plugin != null )
6983         {
6984             Xpp3Dom xpp3Dom = (Xpp3Dom) plugin.getConfiguration();
6985             if ( xpp3Dom != null && xpp3Dom.getChild( param ) != null
6986                 && StringUtils.isNotEmpty( xpp3Dom.getChild( param ).getValue() ) )
6987             {
6988                 return xpp3Dom.getChild( param ).getValue();
6989             }
6990         }
6991 
6992         return null;
6993     }
6994 
6995     /**
6996      * Construct the output file for the generated javadoc-options XML file, after creating the
6997      * javadocOptionsDir if necessary. This method does NOT write to the file in question.
6998      *
6999      * @return The options {@link File} file.
7000      * @since 2.7
7001      */
7002     protected final File getJavadocOptionsFile()
7003     {
7004         if ( javadocOptionsDir != null && !javadocOptionsDir.exists() )
7005         {
7006             javadocOptionsDir.mkdirs();
7007         }
7008 
7009         return new File( javadocOptionsDir, "javadoc-options-" + getAttachmentClassifier() + ".xml" );
7010     }
7011 
7012     /**
7013      * Generate a javadoc-options XML file, for either bundling with a javadoc-resources artifact OR
7014      * supplying to a distro module in a includeDependencySources configuration, so the javadoc options
7015      * from this execution can be reconstructed and merged in the distro build.
7016      *
7017      * @return {@link JavadocOptions}
7018      * @throws IOException {@link IOException}
7019      * @since 2.7
7020      */
7021     protected final JavadocOptions buildJavadocOptions()
7022         throws IOException
7023     {
7024         JavadocOptions options = new JavadocOptions();
7025 
7026         options.setBootclasspathArtifacts( toList( bootclasspathArtifacts ) );
7027         options.setDocfilesSubdirsUsed( docfilessubdirs );
7028         options.setDocletArtifacts( toList( docletArtifact, docletArtifacts ) );
7029         options.setExcludedDocfilesSubdirs( excludedocfilessubdir );
7030         options.setExcludePackageNames( toList( excludePackageNames ) );
7031         options.setGroups( toList( groups ) );
7032         options.setLinks( links );
7033         options.setOfflineLinks( toList( offlineLinks ) );
7034         options.setResourcesArtifacts( toList( resourcesArtifacts ) );
7035         options.setTagletArtifacts( toList( tagletArtifact, tagletArtifacts ) );
7036         options.setTaglets( toList( taglets ) );
7037         options.setTags( toList( tags ) );
7038 
7039         if ( getProject() != null && getJavadocDirectory() != null )
7040         {
7041             options.setJavadocResourcesDirectory(
7042                 toRelative( getProject().getBasedir(), getJavadocDirectory().getAbsolutePath() ) );
7043         }
7044 
7045         File optionsFile = getJavadocOptionsFile();
7046 
7047         try ( Writer writer = WriterFactory.newXmlWriter( optionsFile ) )
7048         {
7049             new JavadocOptionsXpp3Writer().write( writer, options );
7050         }
7051 
7052         return options;
7053     }
7054 
7055     /**
7056      * Override this if you need to provide a bundle attachment classifier, as in the case of test
7057      * javadocs.
7058      * @return The attachment classifier.
7059      */
7060     protected String getAttachmentClassifier()
7061     {
7062         return JAVADOC_RESOURCES_ATTACHMENT_CLASSIFIER;
7063     }
7064 
7065     /**
7066      * Logs an error with throwable content only if in debug.
7067      *
7068      * @param message The message which should be announced.
7069      * @param t The throwable part of the message.
7070      */
7071     protected void logError( String message, Throwable t )
7072     {
7073         if ( getLog().isDebugEnabled() )
7074         {
7075             getLog().error( message, t );
7076         }
7077         else
7078         {
7079             getLog().error( message );
7080         }
7081     }
7082 
7083     /**
7084      * @param prefix The prefix of the exception.
7085      * @param e The exception.
7086      * @throws MojoExecutionException {@link MojoExecutionException} issue while generating report
7087      */
7088     protected void failOnError( String prefix, Exception e )
7089         throws MojoExecutionException
7090     {
7091         if ( failOnError )
7092         {
7093             if ( e instanceof RuntimeException )
7094             {
7095                 throw (RuntimeException) e;
7096             }
7097             throw new MojoExecutionException( prefix + ": " + e.getMessage(), e );
7098         }
7099 
7100         getLog().error( prefix + ": " + e.getMessage(), e );
7101     }
7102 
7103 
7104     /**
7105      *
7106      * @return List of projects to be part of aggregated javadoc
7107      */
7108     private List<MavenProject> getAggregatedProjects()
7109     {
7110         if ( this.reactorProjects == null )
7111         {
7112            return Collections.emptyList();
7113         }
7114         Map<Path, MavenProject> reactorProjectsMap = new HashMap<>();
7115         for ( MavenProject reactorProject : this.reactorProjects )
7116         {
7117             if ( !isSkippedJavadoc( reactorProject ) && //
7118                     !isSkippedModule( reactorProject ) )
7119             {
7120                 reactorProjectsMap.put( reactorProject.getBasedir().toPath(), reactorProject );
7121             }
7122         }
7123 
7124         return new ArrayList<>( modulesForAggregatedProject( project, reactorProjectsMap ) );
7125     }
7126 
7127     /**
7128      *
7129      * @return <code>true</code> if the module need to be skipped from aggregate generation
7130      */
7131     protected boolean isSkippedModule( MavenProject mavenProject )
7132     {
7133         if ( StringUtils.isEmpty( this.skippedModules ) )
7134         {
7135             return false;
7136         }
7137         List<String> modulesToSkip = Arrays.asList( StringUtils.split( this.skippedModules, ',' ) );
7138         return modulesToSkip.contains( mavenProject.getArtifactId() );
7139     }
7140 
7141     /**
7142      *
7143      * @return <code>true</code> if the pom configuration skip javadoc generation for the project
7144      */
7145     protected boolean isSkippedJavadoc( MavenProject mavenProject )
7146     {
7147         String property = mavenProject.getProperties().getProperty( "maven.javadoc.skip" );
7148         if ( property != null )
7149         {
7150             boolean skip = BooleanUtils.toBoolean( property );
7151             getLog().debug( "isSkippedJavadoc " + mavenProject + " " + skip );
7152             return skip;
7153         }
7154         final String pluginId = "org.apache.maven.plugins:maven-javadoc-plugin";
7155         property = getPluginParameter( mavenProject, pluginId, "skip" );
7156         if ( property != null )
7157         {
7158             boolean skip = BooleanUtils.toBoolean( property );
7159             getLog().debug( "isSkippedJavadoc " + mavenProject + " " + skip );
7160             return skip;
7161         }
7162         if ( mavenProject.getParent() != null )
7163         {
7164             return isSkippedJavadoc( mavenProject.getParent() );
7165         }
7166         getLog().debug( "isSkippedJavadoc " + mavenProject + " " + false );
7167         return false;
7168     }
7169 
7170 }